Peter Oakes
Published

Raspberry Pi 2, 480*320 TFT LCD Displays Windows 10, SPI

PI 2, TFT LCD 480*320, SPI & Touch Screen on Windows 10 IoT, working demo, more can be found at https://www.youtube.com/c/thebreadboardca

IntermediateFull instructions provided25,755
Raspberry Pi 2, 480*320 TFT LCD Displays Windows 10, SPI

Things used in this project

Hardware components

Raspberry Pi 2 Model B
Raspberry Pi 2 Model B
×1
waveshare 3.2" LCD with touch
×1
Waveshare 4" Raspberry PI LCD display
×1

Story

Read more

Schematics

LCD PI Display

LCD Wave share 4" Display directly on the Raspberry PI

Code

mainpage.xaml

C#
<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>

MainPage.xaml.cs

C#
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;
        }


}
}

WS_ILI9488.cs

C#
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

PI Colour LCD.zip

Plain text
No preview (download only).

Credits

Peter Oakes

Peter Oakes

6 projects • 37 followers
Electronics Engineer, Programmer, and love to make Videos on Electronics Engineering related stuff and post on my YouTube Channel "thebreadboardca"

Comments