Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 |
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 limitations 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 various images I happen to randomly pick off the internet to demonstrate the ability 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.
The code is attached so enjoy and please ask questions
This represents a lot of effort so please be respectful of your feedback, you can use it as you please and I look forward to comments and suggestions for improvement, there are areas for improvement and optimization I know and not all the error handling and bounds checking are in place but it works well and it seems to be stable so I hope you find use for it and if possible, you extend it and re-share with the community.
I hope you find use for it!
<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;
}
}
}
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
Comments