OK, so i think I am outdoing myself with this one
ILI9488 480 * 320 LCD 65K colour display
TSC2046 touch screen cotroller
ILI 9341 320 * 240 LCD 65K colour display
HX8357 480 * 320 LCD 65K colour display (Not tested but converted still)
8*6 Font integrated
11* 16 font converted but not integrated
line draw
Arc, Circle
Character from Font
Pixel Point
Image Load etc
wow, some coding marathon there
so following the demo by Graham Chow who provided the basic initialization routines for a 9341 display, I took it alot further and added basic vector functions like line draw, pixel draw and character draw routines along with optimized SPI transfer routines resulting in the provieded set of libraries supporting a few more display chips
there not perfect, but are functional, they probably need more bounds checking and limit checks but they do work well and as fast as an be considering the limitation s of the MS SPI implementation
Here is the UI
all the buttons work to the smaller display area in the top right of the main display (Minions graphic area)
some are pretty obvious as to their function, the smaller sub buttons load varios images I happen to randomly pick of the internet to demonstrate the abilit to load to the LCD SPi screen as well as the HDMI (See the video for a demo)
the DEMO button runs through the same basic set of routines that the Sharp96 demo used. Rectangle fill, line draw, arc, circle and various text writing with different font sizes
The difference here is that all this is also invokeable from the touch screen via a finger or a pen on the touch panel, in addition, if the calibration if the touch screen has not been completed it will invoke on start up and then save the results to the SD card for automatic load at next startup, not only is this hand for the touch screen but shows how any value can be saved o the SD card for future use through restarts etc.
here is the LCD driver code
using System; using System.Threading.Tasks; using Windows.Devices.Enumeration; using Windows.Devices.Spi; using Windows.Devices.Gpio; using Windows.Storage; using Windows.Graphics.Imaging; using Windows.Storage.Streams; //************************************************************************************************************** // Drivers specifically for the Waveshare 4" display where // the display works in RGB parallel mode but has shift registers external to present a SPI interface to the PI // or other connected device, shift registers are 74HC4049 in series to present a 16bit input // also there is a ripple counter 74HC4040 automatically performing a load every 16 SPI clocks so even 8 bit commands // have to be sent 16 bits at a time with the command in the lower 8 bits // // Author Peter Oakes, peter@thebreadboard.ca, August 2015 //************************************************************************************************************** namespace PI_Colour_LCD { public class WS_ILI9488Display { public string currentImage; public UInt16 LCD_VERTICAL_MAX = 320; // Y public UInt16 LCD_HORIZONTAL_MAX = 480; // X public static UInt16 BLACK = 0x0000; public static UInt16 WHITE = 0xFFFF; public static UInt16 VERTICAL_MAX_DEFAULT = 320; // y public static UInt16 HORIZONTAL_MAX_DEFAULT = 480; // X public SpiDevice SpiDisplay; public GpioPin DCPin; public GpioPin ResetPin; public UInt16 cursorX; public UInt16 cursorY; public byte[] DisplayBuffer; // A working pixel buffer for your code public WS_ILI9488Display(string defaultText, int ulValue, int dcpin, int resetpin) { LCD_VERTICAL_MAX = VERTICAL_MAX_DEFAULT; // Y LCD_HORIZONTAL_MAX = HORIZONTAL_MAX_DEFAULT; // X //InitSPI(SpiDevice SPI, int SPIpin, int speed, SpiMode mode, string SPI_CONTROLLER_NAME) DCPin = WS_ILI9488.InitGPIO(dcpin, GpioPinDriveMode.Output, GpioPinValue.High); ResetPin = WS_ILI9488.InitGPIO(resetpin, GpioPinDriveMode.Output, GpioPinValue.High); cursorX = 0; cursorY = 0; DisplayBuffer = new byte[LCD_VERTICAL_MAX * LCD_HORIZONTAL_MAX * 2]; // A working pixel buffer for your code, RGB565 currentImage = ""; } } public static class WS_ILI9488 { private const string SPI_CONTROLLER_NAME = "SPI0"; /* For Raspberry Pi 2, use SPI0 */ // waveshare board uses external shift registers so you need to code like it was 16bit parallel // interface all commands and parameters have to go out in lower byte only, as latch is automatic after 16bits we need to pad everything private static readonly byte padding = 0x00; private static readonly byte[] ILI9488_CMD_SLEEP_OUT = {padding, 0x11 }; private static readonly byte[] ILI9488_CMD_DISPLAY_ON = { padding, 0x29 }; private static readonly byte[] ILI9488_CMD_MEMORY_WRITE = { padding, 0x2C }; private static readonly byte[] ILI9488_CMD_MEMORY_ACCESS_CONTROL = { padding, 0x36 }; private static readonly byte[] ILI9488_CMD_INTERFACE_PIXEL_FORMAT = { padding, 0x3a }; private static readonly byte[] ILI9488_CMD_INTERFACE_MODE_CONTROL = { padding, 0xB0 }; private static readonly byte[] ILI9488_CMD_FRAME_RATE_CONTROL_NORMAL = { padding, 0xb1 }; private static readonly byte[] ILI9488_CMD_DISPLAY_INVERSION_CONTROL = { padding, 0xB4 }; private static readonly byte[] ILI9488_CMD_DISPLAY_FUNCTION_CONTROL = { padding, 0xb6 }; private static readonly byte[] ILI9488_CMD_ENTRY_MODE_SET = { padding, 0xB7 }; private static readonly byte[] ILI9488_CMD_POWER_CONTROL_1 = { padding, 0xC0 }; private static readonly byte[] ILI9488_CMD_POWER_CONTROL_2 = { padding, 0xC1 }; private static readonly byte[] ILI9488_CMD_VCOM_CONTROL_1 = { padding, 0xc5 }; private static readonly byte[] ILI9488_CMD_POSITIVE_GAMMA_CTRL = { padding, 0xe0 }; private static readonly byte[] ILI9488_CMD_NEGATIVE_GAMMA_CTRL = { padding, 0xe1 }; private static readonly byte[] ILI9488_CMD_SET_IMAGE_FUNCTION = { padding, 0xE9 }; private static readonly byte[] ILI9488_CMD_ADJUST_CONTROL_3 = { padding, 0xf7 }; private static readonly byte[] CMD_ENTER_SLEEP = { padding, 0x10 }; private static readonly byte[] CMD_DISPLAY_OFF = { padding, 0x28 }; private static readonly byte[] CMD_GAMMA_SET = { padding, 0x26 }; private static readonly byte[] CMD_COLUMN_ADDRESS_SET = { padding, 0x2a }; private static readonly byte[] CMD_PAGE_ADDRESS_SET = { padding, 0x2b }; private static readonly byte[] CMD_VCOM_CONTROL_2 = { padding, 0xc7 }; private static readonly byte[] CMD_DRIVER_TIMING_CONTROL_A = { padding, 0xe8 }; private static readonly byte[] CMD_DRIVER_TIMING_CONTROL_B = { padding, 0xea }; private static readonly byte[] CMD_POWER_ON_SEQUENCE_CONTROL = { padding, 0xed }; private static readonly byte[] CMD_ENABLE_3G = { padding, 0xf2 }; private static bool AutoWrap = false; public struct Graphics_Rectangle { public int XMin; public int XMax; public int YMin; public int YMax; } // As we are not using a single GPIO pin it makes more sense to have a helper, the user app determins the pins // and keeps track through the display object class instansiated public static GpioPin InitGPIO(int GPIOpin, GpioPinDriveMode mode, GpioPinValue HiLow) { var gpio = GpioController.GetDefault(); // Show an error if there is no GPIO controller if (gpio == null) { return null; } var pin = gpio.OpenPin(GPIOpin); if (pin == null) { return null; } pin.SetDriveMode(mode); pin.Write(HiLow); return pin; } // Initialize the display class creating the SPI references and GPIO needed for operation, User app provides the parameters // you can also provide a default bitmap to load, JPG, GIF, PNG etc it will be scaled to fit public static async Task InitILI9488DisplaySPI(WS_ILI9488Display display, int SPIDisplaypin, int speed, SpiMode mode, string SPI_CONTROLLER_NAME, string DefaultDisplay) { var displaySettings = new SpiConnectionSettings(SPIDisplaypin); displaySettings.ClockFrequency = speed;// 500kHz; displaySettings.Mode = mode; //Mode0,1,2,3; MCP23S17 needs mode 0 string DispspiAqs = SpiDevice.GetDeviceSelector(SPI_CONTROLLER_NAME); var DispdeviceInfo = await DeviceInformation.FindAllAsync(DispspiAqs); display.SpiDisplay = await SpiDevice.FromIdAsync(DispdeviceInfo[0].Id, displaySettings); await ResetDisplay(display); await InitRegisters(display); await wakeup(display); if (String.IsNullOrEmpty(DefaultDisplay)) InitializeDisplayBuffer(display, WS_ILI9488Display.BLACK); else await LoadBitmap(display, DefaultDisplay); Flush(display); } public static async Task ResetDisplay(WS_ILI9488Display display) { // assume power has just been turned on display.ResetPin.Write(GpioPinValue.High); await Task.Delay(10); display.ResetPin.Write(GpioPinValue.Low); // reset await Task.Delay(10); // wait 10 ms display.ResetPin.Write(GpioPinValue.High); // out of reset await Task.Delay(150); } // note all the padding data, this is because of the shift registers, not an issue as it only really affects the commands, not the graphics memory public static async Task InitRegisters(WS_ILI9488Display display) { // MORE PADDING SendCommand(display, ILI9488_CMD_POWER_CONTROL_1); SendData(display, new byte[] { padding, 0x10, padding, 0x10 }); SendCommand(display, ILI9488_CMD_POWER_CONTROL_2); SendData(display, new byte[] { padding, 0x41 }); SendCommand(display, ILI9488_CMD_VCOM_CONTROL_1); SendData(display, new byte[] { padding, 0x00, padding, 0x22, padding, 0x80 }); SendCommand(display, ILI9488_CMD_MEMORY_ACCESS_CONTROL); SendData(display, new byte[] { padding, 0x68 }); SendCommand(display, ILI9488_CMD_INTERFACE_MODE_CONTROL); SendData(display, new byte[] { padding, 0x00 }); SendCommand(display, ILI9488_CMD_FRAME_RATE_CONTROL_NORMAL); SendData(display, new byte[] { padding, 0xB0, padding, 0x11 }); SendCommand(display, ILI9488_CMD_DISPLAY_INVERSION_CONTROL); SendData(display, new byte[] { padding, 0x02 }); SendCommand(display, ILI9488_CMD_INTERFACE_PIXEL_FORMAT); SendData(display, new byte[] { padding, 0x55 }); // the wave share is actually working in 16bit parallel mode SendCommand(display, ILI9488_CMD_SET_IMAGE_FUNCTION); SendData(display, new byte[] { padding, 0x01 }); SendCommand(display, ILI9488_CMD_ENTRY_MODE_SET); SendData(display, new byte[] { padding, 0xC6 }); SendCommand(display, ILI9488_CMD_ADJUST_CONTROL_3); SendData(display, new byte[] { padding, 0xA9, padding, 0x51, padding, 0x2C, padding, 0x82 }); SendCommand(display, ILI9488_CMD_DISPLAY_FUNCTION_CONTROL); SendData(display, new byte[] { padding, 0x00, padding, 0x22, padding, 0x3B }); //SendCommand(display, ILI9488_CMD_POSITIVE_GAMMA_CTRL); SendData(display, new byte[] { padding, 0x00, padding, 0x07, padding, 0x0F, padding, 0x0D, padding, 0x1B, padding, 0x0A, padding, 0x3C, padding, 0x78, padding, 0x4A, padding, 0x07, padding, 0x0E, padding, 0x09, padding, 0x1B, padding, 0x1E, padding, 0x0F }); //SendCommand(display, ILI9488_CMD_NEGATIVE_GAMMA_CTRL); SendData(display, new byte[] { padding, 0x00, padding, 0x22, padding, 0x24, padding, 0x06, padding, 0x12, padding, 0x07, padding, 0x36, padding, 0x47, padding, 0x47, padding, 0x06, padding, 0x0A, padding, 0x07, padding, 0x30, padding, 0x37, padding, 0x0F }); SendCommand(display, ILI9488_CMD_POSITIVE_GAMMA_CTRL); SendData(display, new byte[] { padding, 0x0F, padding, 0x1F, padding, 0x1C, padding, 0x0B, padding, 0x0E, padding, 0x09, padding, 0x48, padding, 0x99, padding, 0x38, padding, 0x0A, padding, 0x14, padding, 0x06, padding, 0x11, padding, 0x09, padding, 0x00 }); SendCommand(display, ILI9488_CMD_NEGATIVE_GAMMA_CTRL); SendData(display, new byte[] { padding, 0x0F, padding, 0x36, padding, 0x2E, padding, 0x09, padding, 0x0A, padding, 0x04, padding, 0x46, padding, 0x66, padding, 0x37, padding, 0x06, padding, 0x10, padding, 0x04, padding, 0x24, padding, 0x20, padding, 0x00 }); } public static async Task wakeup(WS_ILI9488Display display) { SendCommand(display, ILI9488_CMD_SLEEP_OUT); await Task.Delay(150); SendCommand(display, ILI9488_CMD_DISPLAY_ON); } public static void Sleep(WS_ILI9488Display display) { SendCommand(display, CMD_DISPLAY_OFF); SendCommand(display, CMD_ENTER_SLEEP); } public static void CleanUp() { //should really add clean up in here !! } // Start of actual Graphics routines public static async Task landscapeMode(WS_ILI9488Display display) { try { SendCommand(display, ILI9488_CMD_MEMORY_ACCESS_CONTROL); SendData(display, new byte[] { padding, 0x68 }); display.LCD_HORIZONTAL_MAX = WS_ILI9488Display.HORIZONTAL_MAX_DEFAULT; display.LCD_VERTICAL_MAX = WS_ILI9488Display.VERTICAL_MAX_DEFAULT; await LoadBitmap(display, display.currentImage); Flush(display); } catch (Exception ex) { throw new Exception(ex.Message); } } public static async Task PortrateMode(WS_ILI9488Display display) { try { SendCommand(display, ILI9488_CMD_MEMORY_ACCESS_CONTROL); SendData(display, new byte[] { padding, 0x48 }); display.LCD_VERTICAL_MAX = WS_ILI9488Display.HORIZONTAL_MAX_DEFAULT; display.LCD_HORIZONTAL_MAX = WS_ILI9488Display.VERTICAL_MAX_DEFAULT; await LoadBitmap(display, display.currentImage); Flush(display); } catch (Exception ex) { throw new Exception(ex.Message); } } private static void SetAddress(WS_ILI9488Display display, UInt16 x0, UInt16 y0, UInt16 x1, UInt16 y1) { SendCommand(display, CMD_COLUMN_ADDRESS_SET); SendData(display, new byte[] { padding, (byte)(x0 >> 8), padding, (byte)(x0), padding, (byte)(x1 >> 8), padding, (byte)(x1) }); SendCommand(display, CMD_PAGE_ADDRESS_SET); SendData(display, new byte[] { padding, (byte)(y0 >> 8), padding, (byte)(y0), padding, (byte)(y1 >> 8), padding, (byte)(y1) }); } public static ushort RGB24ToRGB565(byte Red, byte Green, byte Blue) { UInt16 red565 = (UInt16)((Red <<8) & 0xF800); UInt16 green565 = (UInt16)((Green <<3) & 0x07E0); UInt16 blue565 = (UInt16)(Blue >>3); return (UInt16)(red565 | green565 | blue565); } public static void InitializeDisplayBuffer(WS_ILI9488Display display, UInt16 colour) { byte hi = (byte)(colour >> 8); byte low = (byte)(colour & 0xFF); for (uint i = 0; i < display.LCD_HORIZONTAL_MAX * display.LCD_VERTICAL_MAX; i++) { display.DisplayBuffer[i*2] = hi; display.DisplayBuffer[i*2+1] = low; } } // loads a bitmap to the full scale of the display buffer but does not show it, you need a flush after public static async Task LoadBitmap(WS_ILI9488Display display, string name) { try { StorageFile srcfile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(name)); using (IRandomAccessStream fileStream = await srcfile.OpenAsync(Windows.Storage.FileAccessMode.Read)) { BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream); BitmapTransform transform = new BitmapTransform() { ScaledWidth = Convert.ToUInt32(display.LCD_HORIZONTAL_MAX), ScaledHeight = Convert.ToUInt32(display.LCD_VERTICAL_MAX) }; PixelDataProvider pixelData = await decoder.GetPixelDataAsync( BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, transform, ExifOrientationMode.RespectExifOrientation, ColorManagementMode.DoNotColorManage ); byte[] sourcePixels = pixelData.DetachPixelData(); if (sourcePixels.Length != display.LCD_HORIZONTAL_MAX * display.LCD_VERTICAL_MAX * 4) return; int pi = 0; for (UInt32 x = 0; x < sourcePixels.Length - 1; x += 4) { // we ignore the alpha value [3] ushort temp = RGB24ToRGB565(sourcePixels[x + 2], sourcePixels[x + 1], sourcePixels[x]); display.DisplayBuffer[pi * 2] = (byte)((temp >> 8) & 0xFF); display.DisplayBuffer[pi * 2 + 1] = (byte)(temp & 0xFF); pi++; } } } catch (Exception ex) { throw new Exception(ex.Message); } display.currentImage = name; } // loads a bitmap directly to the screen area designated by the x1, y1, width and height, it will scale etc automatically public static async Task LoadBitmap(WS_ILI9488Display display, UInt16 x1, UInt16 y1, UInt16 width, UInt16 height, string name) { // basic bounds checking if ((x1+width > display.LCD_HORIZONTAL_MAX) | (y1+height > display.LCD_VERTICAL_MAX)) return; byte[] imagemap; try { StorageFile srcfile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(name)); using (IRandomAccessStream fileStream = await srcfile.OpenAsync(Windows.Storage.FileAccessMode.Read)) { BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream); BitmapTransform transform = new BitmapTransform() { ScaledWidth = Convert.ToUInt32(width), ScaledHeight = Convert.ToUInt32(height) }; PixelDataProvider pixelData = await decoder.GetPixelDataAsync( BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, transform, ExifOrientationMode.RespectExifOrientation, ColorManagementMode.DoNotColorManage ); byte[] sourcePixels = pixelData.DetachPixelData(); if (sourcePixels.Length != width * height * 4) return; // make sure it scaled and loaded right imagemap = new byte[width * height * 2]; // something to put the image in int pi = 0; for (UInt32 x = 0; x < sourcePixels.Length - 1; x += 4) { // we ignore the alpha value [3] ushort temp = RGB24ToRGB565(sourcePixels[x + 2], sourcePixels[x + 1], sourcePixels[x]); imagemap[pi * 2] = (byte)((temp >> 8) & 0xFF); imagemap[pi * 2 + 1] = (byte)(temp & 0xFF); pi++; } } SetAddress(display, x1,y1, (UInt16)(x1+width-1), (UInt16)(y1+height-1)); SendCommand(display, ILI9488_CMD_MEMORY_WRITE); SendData(display, imagemap); } catch (Exception ex) { throw new Exception(ex.Message); } } // send the display buffer to the physical screen, this is not hte fastest due to the current implementation (August 2015) of the SPI driver public static void Flush(WS_ILI9488Display display) { if (display.DisplayBuffer.Length != display.LCD_VERTICAL_MAX * display.LCD_HORIZONTAL_MAX * 2) return; SetAddress(display, 0, 0, (UInt16)(display.LCD_HORIZONTAL_MAX - 1), (UInt16)(display.LCD_VERTICAL_MAX - 1)); int block_size = 51200; // limits of the SPI interface is 64K but this is an even block for display ??? int numBlocks = display.DisplayBuffer.Length / block_size; byte[] buffer = new byte[block_size]; // now we start to write the buffer out SendCommand(display, ILI9488_CMD_MEMORY_WRITE); for (int block = 0; block < numBlocks; block++) { Array.Copy(display.DisplayBuffer, block * block_size, buffer, 0, block_size); SendData(display, buffer); } } // send data to the display, it does not assume where the data has to go so you need to issue the appropriate command before calling this // the routine is optimized for the SPI bus limit of 64K bytes MAX per write private static void SendData(WS_ILI9488Display display, byte[] Data) { display.DCPin.Write(GpioPinValue.High); int blocksize = 65536; if (Data.Length > blocksize) { int numBlocks = Data.Length / blocksize; byte[] buffer = new byte[blocksize]; for (int block = 0; block < numBlocks; block++) { Array.Copy(Data, block * blocksize, buffer, 0, blocksize); display.SpiDisplay.Write(buffer); } if (numBlocks * numBlocks < Data.Length) { // we have a bit more to send byte[] bufferlast = new byte[Data.Length - (numBlocks * blocksize)]; Array.Copy(Data, Data.Length - bufferlast.Length, bufferlast, 0, bufferlast.Length); display.SpiDisplay.Write(bufferlast); } } else { display.SpiDisplay.Write(Data); } } // commands are never big so no need for chunking the data stream private static void SendCommand(WS_ILI9488Display display, byte[] Command) { display.DCPin.Write(GpioPinValue.Low); display.SpiDisplay.Write(Command); } // these routines are optimized for the hardware so not generic public static void PixelDraw(WS_ILI9488Display display, UInt16 x, UInt16 y, int color) { if ((x < 0) || (x >= display.LCD_HORIZONTAL_MAX) || (y < 0) || (y >= display.LCD_VERTICAL_MAX)) return; byte hi = (byte)(color >> 8), low = (byte)(color & 0xff); SetAddress(display, x, y, (UInt16)(x + 1), (UInt16)(y +1)); SendCommand(display, ILI9488_CMD_MEMORY_WRITE); SendData(display, new byte[] { hi,low }); } public static void LineDrawH(WS_ILI9488Display display, UInt16 x, UInt16 y, UInt16 width, UInt16 color) { // Rudimentary clipping if ((x >= display.LCD_HORIZONTAL_MAX) || (y >= display.LCD_VERTICAL_MAX)) return; if ((x + width - 1) >= display.LCD_HORIZONTAL_MAX) width = (UInt16)(display.LCD_HORIZONTAL_MAX - x); // this should set a 1 row high line on the screen for writing to SetAddress(display, x, y, (UInt16)(x + width - 1), y); byte hi = (byte)(color >> 8); byte low = (byte)(color & 0xFF); byte[] tmparray = new byte[width * 2]; //blast through creating the line for (int col = 0; col < tmparray.Length; col += 2) { tmparray[col + 1] = low; tmparray[col] = hi; } //send asap to the screen SendCommand(display, ILI9488_CMD_MEMORY_WRITE); SendData(display, tmparray); } public static void LineDrawV(WS_ILI9488Display display, UInt16 x, UInt16 y, UInt16 height, UInt16 color) { // Rudimentary clipping if ((x >= display.LCD_HORIZONTAL_MAX) || (y >= display.LCD_VERTICAL_MAX)) return; if ((y + height - 1) >= display.LCD_VERTICAL_MAX) height = (UInt16)(display.LCD_VERTICAL_MAX - y); // this should set a 1 column wide line on the screen for writing to SetAddress(display, x, y, x, (UInt16)(y + height - 1)); byte hi = (byte)(color >> 8); byte low = (byte)(color & 0xFF); byte[] tmparray = new byte[height * 2]; //blast through creating the line for (int row = 0; row < tmparray.Length; row+=2) { tmparray[row] = hi; tmparray[row+1] = low; } //send asap to the screen SendCommand(display, ILI9488_CMD_MEMORY_WRITE); SendData(display, tmparray); } // fill a rectangle. x, y is the top left corner, width and height is the size public static void fillRect(WS_ILI9488Display display, UInt16 x, UInt16 y, UInt16 width, UInt16 height, int color) { // Rudimentary clipping if ((x >= display.LCD_HORIZONTAL_MAX) || (y >= display.LCD_VERTICAL_MAX)) return; if ((x + width - 1) >= display.LCD_HORIZONTAL_MAX) width = (UInt16)(display.LCD_HORIZONTAL_MAX - x); if ((y + height - 1) >= display.LCD_VERTICAL_MAX) height = (UInt16)(display.LCD_VERTICAL_MAX - y); byte hi = (byte)(color >> 8), low = (byte)(color & 0xff); byte[] tmparray = new byte[height * width * 2]; for (int xb = 0; xb < tmparray.Length; xb+=2) { tmparray[xb + 1] = low; tmparray[xb] = hi; } SetAddress(display, x, y, (UInt16)(x + width - 1), (UInt16)(y + height - 1)); SendCommand(display, ILI9488_CMD_MEMORY_WRITE); SendData(display, tmparray); } // generic routines that rely on the above functions (note 0,0 is the top left corner) // draw an arbitary line from x1,y1 to x2,y2, uses draw pixel unless horizontal or vertical, absolute values, not width and height or length public static void drawLine(WS_ILI9488Display display, UInt16 x1, UInt16 y1, UInt16 x2, UInt16 y2, UInt16 ulValue) { UInt16 error, deltaX, deltaY; int yStep; bool SwapXY; // is this a vertical line as we can call an optimized routine for it if (x1 == x2) { // line drawV and H take a delta distance, not an absolute LineDrawV(display, x1, y1, (UInt16)( y2 - y1), ulValue); return; } // is this a horizontal line as we can call an optimized routine for it if (y1 == y2) { // line drawV and H take a delta distance, not an absolute LineDrawH(display, x1, y1, (UInt16)(x2 - x1), ulValue); return; } // Determine if the line is steep. A steep line has more motion in the Y // direction than the X direction. if (((y2 > y1) ? (y2 - y1) : (y1 - y2)) > ((x2 > x1) ? (x2 - x1) : (x1 - x2))) { SwapXY = true; } else { SwapXY = false; } // If the line is steep, then swap the X and Y coordinates. if (SwapXY) { error = x1; x1 = y1; y1 = error; error = x2; x2 = y2; y2 = error; } // // If the starting X coordinate is larger than the ending X coordinate, // then swap the start and end coordinates. // if (x1 > x2) { error = x1; x1 = x2; x2 = error; error = y1; y1 = y2; y2 = error; } // Compute the difference between the start and end coordinates in each axis. deltaX = (UInt16)(x2 - x1); deltaY = (UInt16)((y2 > y1) ? (y2 - y1) : (y1 - y2)); // Initialize the error term to negative half the X delta. error = (UInt16)(-deltaX / 2); // Determine the direction to step in the Y axis when required. if (y1 < y2) yStep = 1; else yStep = -1; // Loop through all the points along the X axis of the line. for (; x1 <= x2; x1++) { // See if this is a steep line. if (SwapXY) { // Plot this point of the line, swapping the X and Y coordinates. PixelDraw(display, y1, x1, ulValue); } else { // Plot this point of the line, using the coordinates as is. PixelDraw(display, x1, y1, ulValue); } // Increment the error term by the Y delta. error += deltaY; // See if the error term is now greater than zero. if (error > 0) { // Take a step in the Y axis. y1 = (UInt16)(y1 + yStep); // this could be a - or a + step // Decrement the error term by the X delta. error -= deltaX; } } } public static void Arc(WS_ILI9488Display display, UInt16 lX, UInt16 lY, UInt16 Radius, UInt16 startAngle, UInt16 endAngle, int ulValue) { ArcEx(display, lX, lY, Radius, startAngle, endAngle, 0.1, ulValue); } // x, y are the center of the Arc public static void ArcEx(WS_ILI9488Display display, UInt16 lX, UInt16 lY, UInt16 Radius, UInt16 startAngle, UInt16 endAngle, double increment, int ulValue) { double startA = startAngle; double endA = endAngle; if (startA > 360) startA = 360; if (endA > 360) endA = 360; if (increment > 10) increment = 10.0; if (increment < 0.1) increment = 0.1; for (double i = startA; i < endA; i += increment) { double angle = i * System.Math.PI / 180; UInt16 x = (UInt16)(lX + Radius * System.Math.Sin(angle)); UInt16 y = (UInt16)(lY - Radius * System.Math.Cos(angle)); PixelDraw(display, x, y, (UInt16)ulValue); } } public static void DrawCircle(WS_ILI9488Display display, UInt16 x0, UInt16 y0, UInt16 radius, UInt16 ulValue) { // Based on https://en.wikipedia.org/wiki/Midpoint_circle_algorithm int x = radius; int y = 0; int decisionOver2 = 1 - x; // Decision criterion divided by 2 evaluated at x=r, y=0 while (x >= y) { PixelDraw(display, (UInt16)(x + x0), (UInt16)(y + y0), ulValue); PixelDraw(display, (UInt16)(y + x0), (UInt16)(x + y0), ulValue); PixelDraw(display, (UInt16)(-x + x0), (UInt16)(y + y0), ulValue); PixelDraw(display, (UInt16)(-y + x0), (UInt16)(x + y0), ulValue); PixelDraw(display, (UInt16)(-x + x0), (UInt16)(-y + y0), ulValue); PixelDraw(display, (UInt16)(-y + x0), (UInt16)(-x + y0), ulValue); PixelDraw(display, (UInt16)(x + x0), (UInt16)(-y + y0), ulValue); PixelDraw(display, (UInt16)(y + x0), (UInt16)(-x + y0), ulValue); y++; if (decisionOver2 <= 0) { decisionOver2 += 2 * y + 1; // Change in decision criterion for y -> y+1 } else { x--; decisionOver2 += 2 * (y - x) + 1; // Change for y -> y+1, x -> x-1 } } } // write a charater array to the physical screen public static void write(WS_ILI9488Display display, char[] c, byte textsize, int ulValue) { for (int len = 0; len < c.Length; len++) { write(display, c[len], textsize, ulValue); } } // write a single character to the physical screen public static void write(WS_ILI9488Display display, char c, byte textsize, int ulValue) { // bounds check if (display.cursorY >= display.LCD_VERTICAL_MAX) return; // were off the screen if (c == '\n') // do we have a new line, if so simply adjust cursor position { display.cursorY += (byte)(textsize * 8); // next line based on font size display.cursorX = 0; // back to character 0 } else if (c == '\r') { display.cursorX = 0; // back to character 0 } else { drawChar(display, display.cursorX, display.cursorY, (byte)c, textsize, ulValue); display.cursorX += (UInt16)(textsize * 6); if (AutoWrap && (display.cursorX > (display.LCD_HORIZONTAL_MAX - textsize * 6))) { display.cursorY += (byte)(textsize * 8); // next line based on font size display.cursorX = 0; // back to character 0 } } } // the actual draw a character from a font file to the physical screen, needs to be extended to support other fonts // draws the character at the Xx, y co-ordinate provided, scaling based on the size property, draws forground colour public static void drawChar(WS_ILI9488Display display, UInt16 x, UInt16 y, byte c, UInt16 size, int ulValue) { if ((x >= display.LCD_HORIZONTAL_MAX) || (y >= display.LCD_VERTICAL_MAX) || ((x + 6 * size - 1) < 0) || ((y + 8 * size - 1) < 0)) return; // bounds checks for (byte i = 0; i < 6; i++) // 5*7 font + space { UInt16 line; // index into character font array if (i == 5) line = 0x0; // space between characters else line = DisplayFont.font[((c * 5) + i)]; for (byte j = 0; j < 8; j++) // now process each bit of the character {// we have a bit and normal colour or no bit and inverted if ( (((line & 0x1) == 1) && (ulValue != 0)) || (((line & 0x1) == 0) && (ulValue == 0)) ) // do we have a bit and black pixels (clear bits) { if (size == 1) // default size PixelDraw(display, (UInt16)(x + i), (UInt16)(y + j), ulValue); else { // scaled up font Graphics_Rectangle pRect; pRect.XMin = x + (i * size); pRect.YMin = y + (j * size); pRect.XMax = pRect.XMin + size - 1; pRect.YMax = pRect.YMin + size - 1; fillRect(display, (UInt16)(x + (i * size)), (UInt16)(y + (j * size)), size, size, ulValue); } } line >>= 1; // next bit in the line } } } public static void setCursor(WS_ILI9488Display display, UInt16 x, UInt16 y) { // needs some bounds checking display.cursorX = (byte)x; display.cursorY = (byte)y; } } // end class } // end namespace
now the Touch screen code
using System; using System.Threading.Tasks; using Windows.Devices.Enumeration; using Windows.Devices.Spi; using System.IO; using Windows.Storage; namespace TouchPanels { //calibration points for touchpanel public class CAL_POINT { public int x; public int y; }; //calibration matrix for touchpanel public class CAL_MATRIX { public DateTime LastUpdated; public int a; public int b; public int c; public int d; public int e; public int f; public int div; public string message; public CAL_MATRIX() { LastUpdated = DateTime.MaxValue ; a = 0; b = 0; c = 0; d = 0; e = 0; f = 0; div = 0; } public CAL_MATRIX (DateTime cal_LastUpdated, int cal_a, int cal_b, int cal_c, int cal_d, int cal_e, int cal_f, int cal_div, string cal_message) { LastUpdated = cal_LastUpdated; a = cal_a; b = cal_b; c = cal_c; d = cal_d; e = cal_e; f = cal_f; div = cal_div; message = cal_message; } // read an array of Cal parameters from a given file name public async Task<bool> SaveCalData(string fileName) { // We change file extension here to make sure it's a .csv file. if (div == 0) return false; // cal data does not valid string[] lines = { LastUpdated.ToString(), a.ToString(), b.ToString(), c.ToString(), d.ToString(), e.ToString(), f.ToString(), div.ToString() }; try { var storageFolder = KnownFolders.DocumentsLibrary; var file = await storageFolder.CreateFileAsync(Path.ChangeExtension(fileName, ".cal"), CreationCollisionOption.ReplaceExisting); await FileIO.WriteTextAsync(file, String.Join(",", lines)); message = "Sucessfully saved Calibration Data"; //File.WriteAllLines(System.IO.Path.ChangeExtension(fileName, ".cal"), lines); return true; // sucess } catch (Exception ex) { message = ex.Message; return false; } } public async Task<bool> LoadCalData(string fileName) { // We change file extension here to make sure it's a .csv file. try { StorageFolder storageFolder = KnownFolders.DocumentsLibrary; StorageFile calFile = await storageFolder.GetFileAsync(System.IO.Path.ChangeExtension(fileName, ".cal")); string CalData = await FileIO.ReadTextAsync(calFile); message = "Sucessfully saved Calibration Data"; string[] data = CalData.Split(new char[] { ','}); // We return a calibration data file with the data in order. LastUpdated = Convert.ToDateTime(data[0]); a = Convert.ToInt32(data[1]); b = Convert.ToInt32(data[2]); c = Convert.ToInt32(data[3]); d = Convert.ToInt32(data[4]); e = Convert.ToInt32(data[5]); f = Convert.ToInt32(data[6]); div = Convert.ToInt32(data[7]); message = (div == 0) ? "Good data" : "Bad Data"; return true; } catch (Exception ex) { LastUpdated = DateTime.MaxValue; a = 0; b = 0; c = 0; d = 0; e = 0; f = 0; div = 0; message = ex.Message; return false; } } }; public static class TSC2046 { public static int lcd_orientation; //lcd_orientation public static int lcd_x, lcd_y; //calibrated pos (screen) public static int tp_x, tp_y; //raw pos (touch panel) public static int tp_last_x, tp_last_y; //last raw pos (touch panel) public static CAL_MATRIX CalibrationMatrix = new CAL_MATRIX(); //calibrate matrix public static int pressure; //touch panel pressure public static int MIN_PRESSURE = 5; //minimum pressure 1...254 public static int LCD_WIDTH = 480; public static int LCD_HEIGHT = 320; public static int CS_PIN = 1; public static byte CMD_START = 0x80; public static byte CMD_12BIT = 0x00; public static byte CMD_8BIT = 0x08; public static byte CMD_DIFF = 0x00; public static byte CMD_SINGLE = 0x04; public static byte CMD_X_POS = 0x10; public static byte CMD_Z1_POS = 0x30; public static byte CMD_Z2_POS = 0x40; public static byte CMD_Y_POS = 0x50; public static byte CMD_PWD = 0x00; public static byte CMD_ALWAYSON = 0x03; public static SpiDevice touchSPI; public static async Task InitTSC2046SPI() { var touchSettings = new SpiConnectionSettings(CS_PIN); touchSettings.ClockFrequency = 125000; touchSettings.Mode = SpiMode.Mode0; //Mode0,1,2,3; MCP23S17 needs mode 0 string DispspiAqs = SpiDevice.GetDeviceSelector( "SPI0"); var DispdeviceInfo = await DeviceInformation.FindAllAsync(DispspiAqs); touchSPI = await SpiDevice.FromIdAsync(DispdeviceInfo[0].Id, touchSettings); //set vars CalibrationMatrix.div = 0; tp_x = 0; tp_y = 0; tp_last_x = 0; tp_last_y = 0; lcd_x = 0; lcd_y = 0; pressure = 0; setOrientation(0); } public static void setOrientation(UInt16 Orientation) { switch (Orientation) { default: case 0: lcd_orientation = 0; break; case 90: lcd_orientation = 90; break; case 180: lcd_orientation = 180; break; case 270: lcd_orientation = 270; break; } } public static void setRotation(UInt16 r) { setOrientation(r); } public static int setCalibration(CAL_POINT[] lcd, CAL_POINT[] tp) { CalibrationMatrix.div = ((tp[0].x - tp[2].x) * (tp[1].y - tp[2].y)) - ((tp[1].x - tp[2].x) * (tp[0].y - tp[2].y)); if (CalibrationMatrix.div == 0) { return 0; } CalibrationMatrix.a = ((lcd[0].x - lcd[2].x) * (tp[1].y - tp[2].y)) - ((lcd[1].x - lcd[2].x) * (tp[0].y - tp[2].y)); CalibrationMatrix.b = ((tp[0].x - tp[2].x) * (lcd[1].x - lcd[2].x)) - ((lcd[0].x - lcd[2].x) * (tp[1].x - tp[2].x)); CalibrationMatrix.c = (tp[2].x * lcd[1].x - tp[1].x * lcd[2].x) * tp[0].y + (tp[0].x * lcd[2].x - tp[2].x * lcd[0].x) * tp[1].y + (tp[1].x * lcd[0].x - tp[0].x * lcd[1].x) * tp[2].y; CalibrationMatrix.d = ((lcd[0].y - lcd[2].y) * (tp[1].y - tp[2].y)) - ((lcd[1].y - lcd[2].y) * (tp[0].y - tp[2].y)); CalibrationMatrix.e = ((tp[0].x - tp[2].x) * (lcd[1].y - lcd[2].y)) - ((lcd[0].y - lcd[2].y) * (tp[1].x - tp[2].x)); CalibrationMatrix.f = (tp[2].x * lcd[1].y - tp[1].x * lcd[2].y) * tp[0].y + (tp[0].x * lcd[2].y - tp[2].x * lcd[0].y) * tp[1].y + (tp[1].x * lcd[0].y - tp[0].x * lcd[1].y) * tp[2].y; CalibrationMatrix.LastUpdated = DateTime.Now; return 1; } public static void TouchToDispAdjust() { if (CalibrationMatrix.div == 0) return; // we have not calibrated the PEN int x = 0, y = 0; //calc x pos if (tp_x != tp_last_x) { tp_last_x = tp_x; x = tp_x; x = ((CalibrationMatrix.a * x) + (CalibrationMatrix.b * y) + CalibrationMatrix.c) / CalibrationMatrix.div; if (x < 0) { x = 0; } else if (x >= LCD_WIDTH) { x = LCD_WIDTH - 1; } lcd_x = x; } //calc y pos if (tp_y != tp_last_y) { tp_last_y = tp_y; y = tp_y; y = ((CalibrationMatrix.d * x) + (CalibrationMatrix.e * y) + CalibrationMatrix.f) / CalibrationMatrix.div; if (y < 0) { y = 0; } else if (y >= LCD_HEIGHT) { y = LCD_HEIGHT - 1; } lcd_y = y; } } public static int getDispX() { TouchToDispAdjust(); switch (lcd_orientation) { case 0: return lcd_x; case 90: return lcd_y; case 180: return LCD_WIDTH - lcd_x; case 270: return LCD_HEIGHT - lcd_y; } return 0; } public static int getDispY() { TouchToDispAdjust(); switch (lcd_orientation) { case 0: return lcd_y; case 90: return LCD_WIDTH - lcd_x; case 180: return LCD_HEIGHT - lcd_y; case 270: return lcd_x; } return 0; } public static int getTouchX() { return tp_x; } public static int getTouchY() { return tp_y; } public static int getPressure() { return pressure; } public static void CheckTouch() { int p, a1, a2, b1, b2; int x, y; byte[] writeBuffer24 = new byte[3]; byte[] readBuffer24 = new byte[3]; //get pressure first to see if the screen is being touched writeBuffer24[0] = (byte)(CMD_START | CMD_8BIT | CMD_DIFF | CMD_Z1_POS); touchSPI.TransferFullDuplex(writeBuffer24, readBuffer24); a1 = readBuffer24[1] & 0x7F; writeBuffer24[0] = (byte)(CMD_START | CMD_8BIT | CMD_DIFF | CMD_Z2_POS); touchSPI.TransferFullDuplex(writeBuffer24, readBuffer24); b1 = 255 - readBuffer24[1] & 0x7F; p = a1 + b1; if (p > MIN_PRESSURE) { //using 2 samples for x and y position //get X data writeBuffer24[0] = (byte)(CMD_START | CMD_12BIT | CMD_DIFF | CMD_X_POS); touchSPI.TransferFullDuplex(writeBuffer24, readBuffer24); a1 = readBuffer24[1]; b1 = readBuffer24[2]; writeBuffer24[0] = (byte)(CMD_START | CMD_12BIT | CMD_DIFF | CMD_X_POS); touchSPI.TransferFullDuplex(writeBuffer24, readBuffer24); a2 = readBuffer24[1]; b2 = readBuffer24[2]; if (a1 == a2) { x = ((a2 << 4) | (b2 >> 4)); //12bit: ((a<<4)|(b>>4)) //10bit: ((a<<2)|(b>>6)) //get Y data writeBuffer24[0] = (byte)(CMD_START | CMD_12BIT | CMD_DIFF | CMD_Y_POS); touchSPI.TransferFullDuplex(writeBuffer24, readBuffer24); a1 = readBuffer24[1]; b1 = readBuffer24[2]; writeBuffer24[0] = (byte)(CMD_START | CMD_12BIT | CMD_DIFF | CMD_Y_POS); touchSPI.TransferFullDuplex(writeBuffer24, readBuffer24); a2 = readBuffer24[1]; b2 = readBuffer24[2]; if (a1 == a2) { y = ((a2 << 4) | (b2 >> 4)); //12bit: ((a<<4)|(b>>4)) //10bit: ((a<<2)|(b>>6)) if ( x >0 && y >0) { tp_x = x; tp_y = y; } pressure = p; } } } else { pressure = 0; } } } // end class } // end namespace
and finally the sreen code XAML then cs
<Page x:Class="RPI2_WIN10_IOT_LCD.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:RPI2_WIN10_IOT_LCD" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid BorderBrush="#FF0393B9" BorderThickness="5,5,5,5" CornerRadius="10" Width="1680" Height="1050" Background="White" > <Image x:Name="image" HorizontalAlignment="Left" Height="233.5" Margin="-45.25,323.75,0,0" VerticalAlignment="Top" Width="853" RenderTransformOrigin="0.5,0.5" Source="Assets/Element14 Logo.png" UseLayoutRounding="False" d:LayoutRounding="Auto"> <Image.RenderTransform> <CompositeTransform Rotation="-90"/> </Image.RenderTransform> </Image> <Button x:Name="Red" Content="FILL RED" HorizontalAlignment="Left" Margin="30,34,0,0" Width="275" Height="140" VerticalAlignment="Top" Background="#FF0000" Click="Red_Click" FontSize="48" FontWeight="Bold"/> <Button x:Name="Green" Content="FILL GREEN" HorizontalAlignment="Left" Margin="30,174,0,0" Width="275" Height="141" VerticalAlignment="Top" Background="Lime" Click="Green_Click" FontSize="48" FontWeight="Bold"/> <Button x:Name="Blue" Content="FILL BLUE" HorizontalAlignment="Left" Margin="30,315,0,0" Width="275" Height="140" VerticalAlignment="Top" Background="Blue" Foreground="White" Click="Blue_Click" FontSize="48" FontWeight="Bold"/> <Button x:Name="Calibrate" Content="Calibrate" HorizontalAlignment="Left" Margin="30,455,0,0" Width="275" Height="138" VerticalAlignment="Top" Click="Calibrate_Click" FontSize="48" FontWeight="Bold" Foreground="White" Background="#FFF57E22" BorderBrush="#FF0093B9"/> <Button x:Name="btnDemo" Content="Demo" HorizontalAlignment="Left" Margin="30,593,0,0" Width="275" Height="141" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Foreground="White" Background="#FFF57E22" BorderBrush="#FF0093B9" Click="btnDemo_Click"/> <Button x:Name="btnLandscape" Content="N/A" HorizontalAlignment="Left" Margin="30,734,0,0" Width="275" Height="140" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Foreground="White" Background="#FFF57E22" BorderBrush="#FF0093B9"/> <Button x:Name="Exit" Content="Quit App" HorizontalAlignment="Left" Margin="30,874,0,0" Width="275" Height="141" VerticalAlignment="Top" FontSize="48" FontWeight="Bold" Foreground="White" Background="#FFF57E22" BorderBrush="#FF0093B9" Click="Exit_Click" /> <TextBox x:Name="Status" HorizontalAlignment="Left" Margin="342,843,0,0" TextWrapping="Wrap" Text="Status" VerticalAlignment="Top" Height="182" Width="1309"/> <Border x:Name="imageArea" BorderBrush="#FF0393B9" BorderThickness="3" HorizontalAlignment="Left" Width="1024" Height="768" Margin="640,5,0,0" VerticalAlignment="Top" CornerRadius="10" Background="White" /> <Button x:Name="Image1" Content="Load Image 1" HorizontalAlignment="Left" Margin="470,34,0,0" VerticalAlignment="Top" Click="Image1_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image2" Content="Load Image 2" HorizontalAlignment="Left" Margin="470,128,0,0" VerticalAlignment="Top" Click="Image2_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image3" Content="Load Image 3" HorizontalAlignment="Left" Margin="470,219,0,0" VerticalAlignment="Top" Click="Image3_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image4" Content="Load Image 4" HorizontalAlignment="Left" Margin="470,315,0,0" VerticalAlignment="Top" Click="Image4_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image5" Content="Load Image 5" HorizontalAlignment="Left" Margin="470,409,0,0" VerticalAlignment="Top" Click="Image5_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image6" Content="Load Image 6" HorizontalAlignment="Left" Margin="470,498,0,0" VerticalAlignment="Top" Click="Image6_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image7" Content="Load Image 7" HorizontalAlignment="Left" Margin="470,593,0,0" VerticalAlignment="Top" Click="Image7_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <Button x:Name="Image8" Content="Load Image 8" HorizontalAlignment="Left" Margin="470,688,0,0" VerticalAlignment="Top" Click="Image8_Click" Height="80" Width="160" FontSize="21.333" FontWeight="Bold" Background="#FF0393B9"/> <TextBlock x:Name="textBlock1" HorizontalAlignment="Left" Margin="650,793,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="1006" Height="40" RenderTransformOrigin="0.5,0.5" FontSize="26.667" FontWeight="Bold" FontFamily="Arial Black" Text="Brought to you by theBreadboard.ca Youtube.com/c/thebreadboardca" Foreground="#FFFACAA1" TextAlignment="Center"/> <Ellipse x:Name="moveme" HorizontalAlignment="Left" Height="20" Margin="536,803,0,0" Stroke="Black" VerticalAlignment="Top" Width="20"> <Ellipse.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="Black" Offset="0"/> <GradientStop Color="Red" Offset="1"/> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> </Grid> </Page>
using System; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.Devices.Spi; using System.Threading.Tasks; using Windows.Devices.Enumeration; using PI_Colour_LCD; using TouchPanels; using Windows.Security.Cryptography; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 namespace RPI2_WIN10_IOT_LCD { /// <summary> /// An empty page that can be used on its own or navigated to within a Frame. /// </summary> public sealed partial class MainPage : Page { private DispatcherTimer timer; // create a timer const Int32 DisplayCS = 0; // 0 maps to CS0 on the Rpi2 */ const Int32 TouchCS = 1; // 1 maps to CS1 on the Rpi2 */ const Int32 DCPIN = 24; // GPIO 22 as it is hard wired on the display im using const Int32 RESETPIN = 25; // GPIO 27 as it is hard wired on the display im using const string Defaulttxt = "Display 1"; const string TSC2046CalFilename = "TSC2046"; WS_ILI9488Display display1 = new WS_ILI9488Display(Defaulttxt, WS_ILI9488Display.BLACK, DCPIN, RESETPIN); //HX8357Display display1 = new HX8357Display(Defaulttxt, HX8357Display.BLACK, DCPIN, RESETPIN); bool penPressed = false; public MainPage() { this.InitializeComponent(); Init(); Status.Text = "Init Success"; } ~MainPage() { WS_HX8357.CleanUp(); } private void initTimer() { // read timer timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(50); //sample every 50mS timer.Tick += Timer_Tick; timer.Start(); } // read GPIO and display it private void Timer_Tick(object sender, object e) { TouchShow(); // do something with the values } private async void Init() { await WS_ILI9488.InitILI9488DisplaySPI(display1, 0, 50000000, SpiMode.Mode0, "SPI0", "ms-appx:///assets/SPLASH1 480.png"); await TSC2046.InitTSC2046SPI(); initTimer(); if (! await TSC2046.CalibrationMatrix.LoadCalData(TSC2046CalFilename)) { calibrateTouch(); } } private void TouchShow() { TSC2046.CheckTouch(); int x = TSC2046.getTouchX(); int y = TSC2046.getTouchY(); int p = TSC2046.getPressure(); if (p > 5) { Status.Text = ("Xraw= " + TSC2046.getTouchX() + " Xcal= " + TSC2046.getDispX() + Environment.NewLine); Status.Text += ("Yraw= " + TSC2046.getTouchY() + " Ycal= " + TSC2046.getDispY() + Environment.NewLine); Status.Text += ("P= " + TSC2046.getPressure() + Environment.NewLine); //move an elypse why not:) moveme.Margin = new Thickness( TSC2046.getDispX() * (1680/480), TSC2046.getDispY()*(1050/320),0,0) ; penPressed = true; } else if (p < 2 && penPressed == true ) { checkAction(TSC2046.getDispX(), TSC2046.getDispY()); penPressed = false; } } private async void button1_Click(object sender, RoutedEventArgs e) { await WS_ILI9488.LoadBitmap(display1, "ms-appx:///assets/TEST.jpg"); WS_ILI9488.Flush(display1); } private void Exit_Click(object sender, RoutedEventArgs e) { App.Current.Exit(); } private void Calibrate_Click(object sender, RoutedEventArgs e) { calibrateTouch(); } private async void calibrateTouch() { //3 point calibration TouchPanels.CAL_POINT[] touchPoints = new CAL_POINT[3]; touchPoints[0] = new CAL_POINT(); touchPoints[1] = new CAL_POINT(); touchPoints[2] = new CAL_POINT(); TouchPanels.CAL_POINT[] screenPoints = new CAL_POINT[3]; screenPoints[0] = new CAL_POINT(); screenPoints[1] = new CAL_POINT(); screenPoints[2] = new CAL_POINT(); WS_ILI9488.fillRect(display1, 0, 0, 480, 320, 0x0000); WS_ILI9488.LineDrawH(display1, 50, 50, 50, 0xFFFF); WS_ILI9488.LineDrawV(display1, 75, 25, 50, 0xFFFF); while (TSC2046.pressure < 5) { TSC2046.CheckTouch(); }//wait for pen pressure screenPoints[0].x = 75; screenPoints[0].y = 50; touchPoints[0].x = TSC2046.tp_x; touchPoints[0].y = TSC2046.tp_y; while (TSC2046.pressure > 1) { TSC2046.CheckTouch(); } // wait for release of pen WS_ILI9488.LineDrawH(display1, 400, 50, 50, 0xFFFF); WS_ILI9488.LineDrawV(display1, 425, 25, 50, 0xFFFF); while (TSC2046.pressure < 5) { TSC2046.CheckTouch(); }//wait for pen pressure screenPoints[1].x = 425; screenPoints[1].y = 50; touchPoints[1].x = TSC2046.tp_x; touchPoints[1].y = TSC2046.tp_y; while (TSC2046.pressure > 1) { TSC2046.CheckTouch(); }// wait for release of pen WS_ILI9488.LineDrawH(display1, 225, 275, 50, 0xFFFF); WS_ILI9488.LineDrawV(display1, 250, 250, 50, 0xFFFF); while (TSC2046.pressure < 5) { TSC2046.CheckTouch(); }//wait for pen pressure screenPoints[2].x = 250; screenPoints[2].y = 275; touchPoints[2].x = TSC2046.tp_x; touchPoints[2].y = TSC2046.tp_y; while (TSC2046.pressure > 1) { TSC2046.CheckTouch(); } // wait for release of pen TSC2046.setCalibration(screenPoints, touchPoints); if (await TSC2046.CalibrationMatrix.SaveCalData(TSC2046CalFilename)) { Status.Text = TSC2046.CalibrationMatrix.message; } else { Status.Text = TSC2046.CalibrationMatrix.message; } WS_ILI9488.Flush(display1); } private async void Green_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/GREEN.png"); } private async void Blue_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/BLUE.png"); } private async void Red_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/RED.png"); } private async void Image1_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/image1 320.png"); } private async void Image2_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/image2 320.png"); } private async void Image3_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/image3 320.png"); } private async void Image4_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/image4 320.jpg"); } private async void Image5_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/image5 320.png"); } private async void Image6_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/Element14 Logo.png"); } private async void Image7_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/Me 320.jpg"); } private async void Image8_Click(object sender, RoutedEventArgs e) { await loadImageToScreen("ms-appx:///assets/Breadboard 320.png"); } private async Task loadImageToScreen(string imageName) { try { await WS_ILI9488.LoadBitmap(display1, 190, 10, 280, 220, imageName); var myBrush = new ImageBrush(); var image = new Image { Source = new BitmapImage(new Uri(imageName)) }; myBrush.ImageSource = image.Source; imageArea.Background = myBrush; } catch (Exception ex) { Status.Text = ex.Message; } } private async Task checkAction(int x, int y) { if (x > 20 && x < 90 && y > 20 && y < 320) { // first column of buttons int button = (y-20) /(320/8); switch (button) { case 0: await loadImageToScreen("ms-appx:///assets/RED.png"); break; case 1: await loadImageToScreen("ms-appx:///assets/GREEN.png"); break; case 2: await loadImageToScreen("ms-appx:///assets/BLUE.png"); break; case 3: calibrateTouch(); break; case 4: await doDemo(); break; case 5: break; case 6: App.Current.Exit(); break; } } else if (x > 140 && x < 180 && y > 20 && y < 229) { // second column of buttons int button = (y - 20) / (229 /9); switch (button) { case 0: await loadImageToScreen("ms-appx:///assets/image1 320.png"); break; case 1: await loadImageToScreen("ms-appx:///assets/image2 320.png"); break; case 2: await loadImageToScreen("ms-appx:///assets/image3 320.png"); break; case 3: await loadImageToScreen("ms-appx:///assets/image4 320.jpg"); break; case 4: await loadImageToScreen("ms-appx:///assets/image5 320.png"); break; case 5: await loadImageToScreen("ms-appx:///assets/Element14 Logo.png"); break; case 6: await loadImageToScreen("ms-appx:///assets/Me 320.jpg"); break; case 7: await loadImageToScreen("ms-appx:///assets/Breadboard 320.png"); break; } } } private async Task doDemo() { int LoopDelay = 1000; try { WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); WS_ILI9488.fillRect(display1, 220, 40, 220, 160, 0xFFFF); WS_ILI9488.fillRect(display1, 250, 70, 160, 100, 0x0000); WS_ILI9488.fillRect(display1, 280, 100, 100, 40, 0xFFFF); await Task.Delay((int)LoopDelay); WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); for (UInt16 x = 0; x < 220 / 2; x += 4) { UInt16 x1 = (UInt16)(x + 190); UInt16 y1 = (UInt16)(x + 10); UInt16 x2 = (UInt16)((280 - x * 2)); UInt16 y2 = (UInt16)((220 - x * 2)); WS_ILI9488.LineDrawH(display1, x1, y1, x2, 0xFFFF); WS_ILI9488.LineDrawH(display1, x1, (UInt16)(y1 + y2), x2, 0xFFFF); WS_ILI9488.LineDrawV(display1, x1, y1, y2, 0xFFFF); WS_ILI9488.LineDrawV(display1, (UInt16)(x1 + x2), y1, y2, 0xFFFF); } await Task.Delay((int)LoopDelay); WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); for (int x = 0; x < 50; x++) { int x1 = (190 + GenerateRndNumber()); int y1 = (10 + GenerateRndNumber()); int x2 = (190 + GenerateRndNumber()); int y2 = (10 + GenerateRndNumber()); WS_ILI9488.drawLine(display1, (UInt16)x1, (UInt16)y1, (UInt16)x2, (UInt16)y2, 0xFe1F); } await Task.Delay((int)LoopDelay); WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); WS_ILI9488.Arc(display1, 275, 75, 120, 90, 180, 0xFFFF); WS_ILI9488.Arc(display1, 375, 175, 120, 270, 360, 0xFFFF); WS_ILI9488.Arc(display1, 275, 175, 120, 0, 90, 0xFFFF); WS_ILI9488.Arc(display1, 375, 75, 120, 180, 270, 0xFFFF); WS_ILI9488.DrawCircle(display1, 325, 125, 100, 0xF81F); await Task.Delay((int)LoopDelay); WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); WS_ILI9488.setCursor(display1, 195, 30); WS_ILI9488.write(display1, "Hello".ToCharArray(), 2, 0xFFFF); WS_ILI9488.setCursor(display1, 195, 47); WS_ILI9488.write(display1, "Hi There".ToCharArray(), 1, 0xFFFF); await Task.Delay((int)LoopDelay); WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); WS_ILI9488.setCursor(display1, 195, 30); WS_ILI9488.write(display1, "Hi There".ToCharArray(), 1, 0xFF00); WS_ILI9488.setCursor(display1, 195, 40); WS_ILI9488.write(display1, "Hi There".ToCharArray(), 2, 0x00FF); WS_ILI9488.setCursor(display1, 195, 70); WS_ILI9488.write(display1, "Hi There".ToCharArray(), 4, 0xFFFF); WS_ILI9488.setCursor(display1, 195, 120); WS_ILI9488.write(display1, "Hi There".ToCharArray(), 6, 0xF81F); await Task.Delay((int)LoopDelay); for (byte x = 1; x < 6; x++) { WS_ILI9488.fillRect(display1, 190, 10, 280, 220, 0x0000); WS_ILI9488.setCursor(display1, 195, 30); WS_ILI9488.write(display1, "Hi there".ToCharArray(), x, 0xFFFF); await Task.Delay((int)LoopDelay / 4); WS_ILI9488.setCursor(display1, 195, 30); WS_ILI9488.write(display1, "Hi there".ToCharArray(), x, 0x0000); } } catch (Exception ex) { throw new Exception(ex.Message); } } private async void btnDemo_Click(object sender, RoutedEventArgs e) { await doDemo(); } public UInt16 GenerateRndNumber() { // Generate a random number. UInt32 Rnd = CryptographicBuffer.GenerateRandomNumber() % 220; //limit between 0 and 220 return (UInt16)Rnd; } } }
the rest of the code and complete solution are attached as a zip file for you pleaseure
this represents a lot of effort an so please be respectful of your feedback, you can use it as you please and i lookforward to comments and suggestions for improvement, ther e are areas for improvement and optimizaron i know and not all the error handling and bounds checkig are in place but it works well and it seems to be stable so I hope ou find use for it and if possible , you extend it and re-share with the community
I hope you find use for it
Peter
Top Comments