Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

README.md

ST7735 Driver with Nucleo STM32F411RET6 Pins Class

No ST7735 Pin Nucleo Pin
1 LED D7
2 SCK SPI2SCK
3 SDA SPI2MOSI
4 A0 (DC) D9
5 RESET D8
6 GND GND
7 VCC 5V

Wiring

alt text

How to use driver / Program.cs

using System;
using System.Threading;
using STM32F4.Pins;
using nanoFramework.Driver;


namespace NFApp5
{
    public class Program
    {
         public static void Main()
         {
            

            ST7735 display = new ST7735(
               NUCLEOF411.Gpio.D8,                 //Reset
               NUCLEOF411.Gpio.D7,                 //BackLight 
               NUCLEOF411.Gpio.D9,                 //A0 (DC) Control Pin / Data Command
               NUCLEOF411.SpiDevice.Sp2.Name,      //SPI SCK/MOSI 
               NUCLEOF411.Gpio.D10                 //chipSelect
              );                          

            display.TurnOn();

            short i = 0;

            display.DrawCircle(20, 20, 20, Color.Red);
            display.DrawRectangle(40, 40, 40, 40, Color.Cyan);
            display.DrawFilledRectangle(80, 80, 40, 40, Color.Blue);
            display.DrawText(10, 30, "Hello nanoFramework", Color.Green);
            display.DrawText(30, 60, "from ST7735 SPI", Color.Green);

            while (true)
            {
                i++;
                display.DrawText(10, 10, i.ToString(),Color.Green);

                Thread.Sleep(500);
            }
        }
        
    }
}

Nucleo F411 Class / STM32F411.cs

//
// Copyright (c) 2019 The nanoFramework project contributors
// See LICENSE file in the project root for full license information.
//
using System;

namespace STM32F4.Pins
{
    public static class Pinouts
    { 
    static int PinNumber(char port, byte pin)
    {
            if (port < 'A' || port > 'J')
                throw new ArgumentException();

            return ((port - 'A') * 16) + pin;
        }
    }

    public static class NUCLEOF411
    {
        /// <summary>
        /// Enumeration of ADC channels available on ST_NUCLEO64_F411RE_NF
        /// </summary>
        public static class AdcChannels
        {
            /// <summary>
            /// Channel 0, exposed as A0, connected to pin 1 on CN8 = PA0 (ADC1 - IN0)
            /// </summary>
            public const int Channel_0 = 0;

            /// <summary>
            /// Channel 1, exposed as A1, connected to pin 2 on CN8 = PA1 (ADC1 - IN1)
            /// </summary>
            public const int Channel_1 = 1;

            /// <summary>
            /// Channel 2, exposed as A2, connected to pin 3 on CN8 = PA4 (ADC1 - IN4)
            /// </summary>
            public const int Channel_2 = 2;

            /// <summary>
            /// Channel 3, exposed as A3, connected to pin 4 on CN8 = PB0 (ADC1 - IN8)
            /// </summary>
            public const int Channel_3 = 3;

            /// <summary>
            /// Channel 4, exposed as A4, connected to pin 5 on CN8 = PC1 (ADC1 - IN11)
            /// </summary>
            public const int Channel_4 = 4;

            /// <summary>
            /// Channel 5, exposed on A5, connected to pin 6 on CN8 = PC0 (ADC1 - IN10)
            /// </summary>
            public const int Channel_5 = 5;

            /// <summary>
            /// Channel 6, internal temperature sensor, connected to ADC1
            /// </summary>
            public const int Channel_TemperatureSensor = 6;

            /// <summary>
            /// Channel 7, internal voltage reference, connected to ADC1
            /// </summary>
            public const int Channel_VrefIn = 7;

            /// <summary>
            /// Channel 8, connected to VBatt pin, ADC1
            /// </summary>
            public const int Channel_Vbatt = 8;
        }

        /// <summary>
        ///  GPIO class represent mapped info and values on 
        ///  Arduino Headers as A0 ... A5 and D0 ... D15 and
        ///  Morpho Headers as PA0 .... PC15 ...
        /// </summary>

        public static class Gpio 
        {
            public const int PA0 = 0;   public const int A0 = 0;
            public const int PA1 = 1;   public const int A1 = 1;  
            public const int PA2 = 2;   public const int D1 = 2;
            public const int PA3 = 3;   public const int D0 = 3;
            public const int PA4 = 4;   public const int A2 = 4;
            public const int PA5 = 5;   public const int D13 = 5;  public const int SCK = 5;     public const int Led1 = 5;
            public const int PA6 = 6;   public const int D12 = 6;  public const int MISO = 6;
            public const int PA7 = 7;   public const int D11 = 7;  public const int MOSI = 7;
            public const int PA8 = 8;   public const int D7 = 8;
            public const int PA9 = 9;   public const int D8 = 9;
            public const int PA10 = 10; public const int D2 = 10;
            public const int PA11 = 11;
            public const int PA12 = 12;
            public const int PA13 = 13;
            public const int PA14 = 14;
            public const int PA15 = 15;

            public const int PB0 = 16;  public const int A3 = 16;
            public const int PB1 = 17; 
            public const int PB2 = 18; 
            public const int PB3 = 19;  public const int D3 = 19;
            public const int PB4 = 20;  public const int D5 = 20;
            public const int PB5 = 21;  public const int D4 = 21;
            public const int PB6 = 22;  public const int D10 = 22;
            public const int PB7 = 23; 
            public const int PB8 = 24;  public const int D15 = 24;  public const int SCL = 24;
            public const int PB9 = 25;  public const int D14 = 25;  public const int SDA = 25;
            public const int PB10 = 26; public const int D6 = 26;
            public const int PB11 = 27;
            public const int PB12 = 28;
            public const int PB13 = 28;
            public const int PB14 = 30;
            public const int PB15 = 31;

            public const int PC0 = 32;  public const int A5 = 32;
            public const int PC1 = 33;  public const int A4 = 22;
            public const int PC2 = 34;
            public const int PC3 = 35;
            public const int PC4 = 36;
            public const int PC5 = 37;
            public const int PC6 = 38;
            public const int PC7 = 39;  public const int D9 = 39;
            public const int PC8 = 40;
            public const int PC9 = 41;
            public const int PC10 = 42;
            public const int PC11 = 43;
            public const int PC12 = 44;
            public const int PC13 = 45; public const int Buttonn1 = 45;
            public const int PC14 = 46;
            public const int PC15 = 47;

        }

        /// <summary>
        ///  this class is represented SPI Device on Nucleo F411 
        /// </summary>
        public static class SpiDevice
        {
            public static class Sp2
            {
                public const string Name = "SPI2";
                public const int MOSI = Gpio.PB15;
                public const int MISO = Gpio.PB14;
                public const int SCK = Gpio.PB13;
            }
        }

        /// <summary>
        ///  this clcass is represented I2C Device on Nucleo F411
        /// </summary>
        public class I2CDevice
        {
            struct I2C1
            {
                public const string Name = "I2C1";
                public const int SCL = 24;
                public const int SDA = 25;
            }
        }

    }
}

ST7735 Driver / ST7735.cs

using System;
using System.Threading;
using Windows.Devices.Gpio;
using Windows.Devices.Spi;

namespace nanoFramework.Driver
{
    /* source code taken from there 
     * https://github.com/bauland/TinyClrLib/blob/master/Modules/Gadgeteer/DisplayN18/
     * and modified to work with nanoFramework 
     */
    public class ST7735
    {
        private readonly SpiDevice _spi;
        private readonly GpioPin _controlPin;
        private readonly GpioPin _resetPin;
        private readonly GpioPin _backlightPin;
        private readonly GpioPin _chipSelectLine;

        private readonly byte[] _buffer1;
        private readonly byte[] _buffer2;
        private readonly byte[] _buffer4;
        private readonly byte[] _clearBuffer;
        private readonly byte[] _characterBuffer1;
        private readonly byte[] _characterBuffer2;
        private readonly byte[] _characterBuffer4;

        private const byte St7735Madctl = 0x36;
        private const byte MadctlMy = 0x80;
        private const byte MadctlMx = 0x40;
        private const byte MadctlMv = 0x20;
        private const byte MadctlBgr = 0x08;

        /// <summary>
        /// The width of the display in pixels.
        /// </summary>
        public const int Width = 160;

        /// <summary>
        /// The height of the display in pixels.
        /// </summary>
        public const int Height = 128;

        /// <summary>
        /// Constructor of Display ST7735
        /// </summary>
        /// <param name="resetPin">Reset Pin ST7735</param>
        /// <param name="backlightPin">BackLight Pin ST7735</param>
        /// <param name="controlPin">A0/DC Pin ST7735</param>
        /// <param name="spiBusLine">SCK=SPI SCK and SDA=SPI MOSI</param>
        /// <param name="chipSelectPin">CS Pin ST7735</param>
        public ST7735(int resetPin, int backlightPin, int controlPin, string spiLine, int chipSelectPin)
        {
            _buffer1 = new byte[1];
            _buffer2 = new byte[2];
            _buffer4 = new byte[4];
            _clearBuffer = new byte[160 * 2 * 16];
            _characterBuffer1 = new byte[80];
            _characterBuffer2 = new byte[320];
            _characterBuffer4 = new byte[1280];

            _controlPin = GpioController.GetDefault().OpenPin(controlPin);
            _controlPin.SetDriveMode(GpioPinDriveMode.Output);

            _resetPin = GpioController.GetDefault().OpenPin(resetPin);
            _resetPin.SetDriveMode(GpioPinDriveMode.Output);

            _backlightPin = GpioController.GetDefault().OpenPin(backlightPin);
            _backlightPin.SetDriveMode(GpioPinDriveMode.Output);
            _backlightPin.Write(GpioPinValue.Low);

            _chipSelectLine = GpioController.GetDefault().OpenPin(chipSelectPin);
            _chipSelectLine.SetDriveMode(GpioPinDriveMode.Output);


            var spiSettings = new SpiConnectionSettings(_chipSelectLine.PinNumber);
            spiSettings.ClockFrequency = 12000000;
            spiSettings.DataBitLength = 8;
            spiSettings.Mode = SpiMode.Mode0;

            _spi = SpiDevice.FromId(spiLine, spiSettings);

            Reset();

            WriteCommand(0x11); //Sleep exit 
            Thread.Sleep(120);

            // ST7735R Frame Rate
            WriteCommand(0xB1);
            WriteData(0x01); WriteData(0x2C); WriteData(0x2D);
            WriteCommand(0xB2);
            WriteData(0x01); WriteData(0x2C); WriteData(0x2D);
            WriteCommand(0xB3);
            WriteData(0x01); WriteData(0x2C); WriteData(0x2D);
            WriteData(0x01); WriteData(0x2C); WriteData(0x2D);

            WriteCommand(0xB4); // Column inversion 
            WriteData(0x07);

            // ST7735R Power Sequence
            WriteCommand(0xC0);
            WriteData(0xA2); WriteData(0x02); WriteData(0x84);
            WriteCommand(0xC1); WriteData(0xC5);
            WriteCommand(0xC2);
            WriteData(0x0A); WriteData(0x00);
            WriteCommand(0xC3);
            WriteData(0x8A); WriteData(0x2A);
            WriteCommand(0xC4);
            WriteData(0x8A); WriteData(0xEE);

            WriteCommand(0xC5); // VCOM 
            WriteData(0x0E);

            WriteCommand(0x36); // MX, MY, RGB mode
            WriteData(MadctlMx | MadctlMy | MadctlBgr);

            // ST7735R Gamma Sequence
            WriteCommand(0xe0);
            WriteData(0x0f); WriteData(0x1a);
            WriteData(0x0f); WriteData(0x18);
            WriteData(0x2f); WriteData(0x28);
            WriteData(0x20); WriteData(0x22);
            WriteData(0x1f); WriteData(0x1b);
            WriteData(0x23); WriteData(0x37); WriteData(0x00);

            WriteData(0x07);
            WriteData(0x02); WriteData(0x10);
            WriteCommand(0xe1);
            WriteData(0x0f); WriteData(0x1b);
            WriteData(0x0f); WriteData(0x17);
            WriteData(0x33); WriteData(0x2c);
            WriteData(0x29); WriteData(0x2e);
            WriteData(0x30); WriteData(0x30);
            WriteData(0x39); WriteData(0x3f);
            WriteData(0x00); WriteData(0x07);
            WriteData(0x03); WriteData(0x10);

            WriteCommand(0x2a);
            WriteData(0x00); WriteData(0x00);
            WriteData(0x00); WriteData(0x7f);
            WriteCommand(0x2b);
            WriteData(0x00); WriteData(0x00);
            WriteData(0x00); WriteData(0x9f);

            WriteCommand(0xF0); //Enable test command  
            WriteData(0x01);
            WriteCommand(0xF6); //Disable ram power save mode 
            WriteData(0x00);

            WriteCommand(0x3A); //65k mode 
            WriteData(0x05);

            // Rotate
            WriteCommand(St7735Madctl);
            WriteData(MadctlMv | MadctlMy);

            WriteCommand(0x29); //Display on
            Thread.Sleep(50);

            Clear();
        }

        private void WriteData(byte[] data)
        {
            _controlPin.Write(GpioPinValue.High);
            
            if(data.Length>4000)
             {
                int diff = data.Length - 4000;

                byte[] buffer1 = new byte[4000];
                byte[] buffer2 = new byte[diff];

                Array.Copy(data, 0, buffer1, 0, 3999);
                Array.Copy(data, 4000, buffer2, 0, diff-1);

                _spi.Write(buffer1);
                _spi.Write(buffer2);
            }
             else
            { 
                _spi.Write(data);
            }
        }

        private void WriteCommand(byte command)
        {
            _buffer1[0] = command;
            _controlPin.Write(GpioPinValue.Low);
            _spi.Write(_buffer1);
        }

        private void WriteData(byte data)
        {
            _buffer1[0] = data;
            _controlPin.Write(GpioPinValue.High);
            _spi.Write(_buffer1);
        }

        private void Reset()
        {
            _resetPin.Write(GpioPinValue.Low);
            Thread.Sleep(300);
            _resetPin.Write(GpioPinValue.High);
            Thread.Sleep(1000);
        }

        private void SetClip(int x, int y, int width, int height)
        {
            WriteCommand(0x2A);

            _controlPin.Write(GpioPinValue.High);
            _buffer4[1] = (byte)x;
            _buffer4[3] = (byte)(x + width - 1);
            _spi.Write(_buffer4);

            WriteCommand(0x2B);
            _controlPin.Write(GpioPinValue.High);
            _buffer4[1] = (byte)y;
            _buffer4[3] = (byte)(y + height - 1);
            _spi.Write(_buffer4);
        }

        /// <summary>
        /// Clears the Display.
        /// </summary>
        public void Clear()
        {
            SetClip(0, 0, 160, 128);
            WriteCommand(0x2C);

            for (var i = 0; i < 128 / 16; i++)
                WriteData(_clearBuffer);
        }

        /// <summary>
        /// Draws an image.
        /// </summary>
        /// <param name="data">The image as a byte array.</param>
        public void DrawImage(byte[] data)
        {
            if (data == null) throw new ArgumentNullException("data");
            if (data.Length == 0) throw new ArgumentException("data.Length must not be zero.", nameof(data));

            WriteCommand(0x2C);
            WriteData(data);
        }

        /// <summary>
        /// Draws an image at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="image">The image to draw.</param>
        public void DrawImage(int x, int y, Image image)
        {
            if (image == null) throw new ArgumentNullException("image");
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            SetClip(x, y, image.Width, image.Height);
            DrawImage(image.Pixels);
        }

        /// <summary>
        /// Draws a filled rectangle.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="width">The width of the rectangle.</param>
        /// <param name="height">The height of the rectangle.</param>
        /// <param name="color">The color to draw.</param>
        public void DrawFilledRectangle(int x, int y, int width, int height, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), "width must not be negative.");
            if (height < 0) throw new ArgumentOutOfRangeException(nameof(height), "height must not be negative.");

            SetClip(x, y, width, height);

            var data = new byte[width * height * 2];
            for (var i = 0; i < data.Length; i += 2)
            {
                data[i] = (byte)((color.As565 >> 8) & 0xFF);
                data[i + 1] = (byte)((color.As565 >> 0) & 0xFF);
            }

            DrawImage(data);
        }

        /// <summary>
        /// Turns the backlight on.
        /// </summary>
        public void TurnOn()
        {
            _backlightPin.Write(GpioPinValue.High);
        }

        /// <summary>
        /// Turns the backlight off.
        /// </summary>
        public void TurnOff()
        {
            _backlightPin.Write(GpioPinValue.Low);
        }

        /// <summary>
        /// Draws a pixel.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="color">The color to draw.</param>
        public void SetPixel(int x, int y, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            SetClip(x, y, 1, 1);

            _buffer2[0] = (byte)(color.As565 >> 8);
            _buffer2[1] = (byte)(color.As565 >> 0);

            DrawImage(_buffer2);
        }

        /// <summary>
        /// Draws a line.
        /// </summary>
        /// <param name="x0">The x coordinate to start drawing at.</param>
        /// <param name="y0">The y coordinate to start drawing at.</param>
        /// <param name="x1">The ending x coordinate.</param>
        /// <param name="y1">The ending y coordinate.</param>
        /// <param name="color">The color to draw.</param>
        public void DrawLine(int x0, int y0, int x1, int y1, Color color)
        {
            if (x0 < 0) throw new ArgumentOutOfRangeException(nameof(x0), "x0 must not be negative.");
            if (y0 < 0) throw new ArgumentOutOfRangeException(nameof(y0), "y0 must not be negative.");
            if (x1 < 0) throw new ArgumentOutOfRangeException(nameof(x1), "x1 must not be negative.");
            if (y1 < 0) throw new ArgumentOutOfRangeException(nameof(y1), "y1 must not be negative.");

            var steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
            int t, dX, dY, yStep, error;

            if (steep)
            {
                t = x0;
                x0 = y0;
                y0 = t;
                t = x1;
                x1 = y1;
                y1 = t;
            }

            if (x0 > x1)
            {
                t = x0;
                x0 = x1;
                x1 = t;

                t = y0;
                y0 = y1;
                y1 = t;
            }

            dX = x1 - x0;
            dY = Math.Abs(y1 - y0);

            error = (dX / 2);

            if (y0 < y1)
            {
                yStep = 1;
            }
            else
            {
                yStep = -1;
            }

            for (; x0 < x1; x0++)
            {
                if (steep)
                {
                    SetPixel(y0, x0, color);
                }
                else
                {
                    SetPixel(x0, y0, color);
                }

                error -= dY;

                if (error < 0)
                {
                    y0 += (byte)yStep;
                    error += dX;
                }
            }
        }


        //public int Abs(int Values)
        //{
        //    int retValue = 0;
        //    if (Values <= 0) retValue = Values * -1;
        //    if (Values > 0) retValue = Values;

        //    return retValue;
        //}

        /// <summary>
        /// Draws a circle.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="r">The radius of the circle.</param>
        /// <param name="color">The color to draw.</param>
        public void DrawCircle(int x, int y, int r, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (r <= 0) throw new ArgumentOutOfRangeException(nameof(r), "radius must be positive.");

            int f = 1 - r;
            int ddFx = 1;
            int ddFy = -2 * r;
            int dX = 0;
            int dY = r;

            SetPixel(x, y + r, color);
            SetPixel(x, y - r, color);
            SetPixel(x + r, y, color);
            SetPixel(x - r, y, color);

            while (dX < dY)
            {
                if (f >= 0)
                {
                    dY--;
                    ddFy += 2;
                    f += ddFy;
                }

                dX++;
                ddFx += 2;
                f += ddFx;

                SetPixel(x + dX, y + dY, color);
                SetPixel(x - dX, y + dY, color);
                SetPixel(x + dX, y - dY, color);
                SetPixel(x - dX, y - dY, color);

                SetPixel(x + dY, y + dX, color);
                SetPixel(x - dY, y + dX, color);
                SetPixel(x + dY, y - dX, color);
                SetPixel(x - dY, y - dX, color);
            }
        }

        /// <summary>
        /// Draws a rectangle.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="width">The width of the rectangle.</param>
        /// <param name="height">The height of the rectangle.</param>
        /// <param name="color">The color to use.</param>
        public void DrawRectangle(int x, int y, int width, int height, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), "width must not be negative.");
            if (height < 0) throw new ArgumentOutOfRangeException(nameof(height), "height must not be negative.");

            for (var i = x; i < x + width; i++)
            {
                SetPixel(i, y, color);
                SetPixel(i, y + height - 1, color);
            }

            for (var i = y; i < y + height; i++)
            {
                SetPixel(x, i, color);
                SetPixel(x + width - 1, i, color);
            }
        }

        static readonly byte[] Font = new byte[ /* 95 * 5*/ ] {
            0x00, 0x00, 0x00, 0x00, 0x00, /* Space	0x20 */
            0x00, 0x00, 0x4f, 0x00, 0x00, /* ! */
            0x00, 0x07, 0x00, 0x07, 0x00, /* " */
            0x14, 0x7f, 0x14, 0x7f, 0x14, /* # */
            0x24, 0x2a, 0x7f, 0x2a, 0x12, /* $ */
            0x23, 0x13, 0x08, 0x64, 0x62, /* % */
            0x36, 0x49, 0x55, 0x22, 0x20, /* & */
            0x00, 0x05, 0x03, 0x00, 0x00, /* ' */
            0x00, 0x1c, 0x22, 0x41, 0x00, /* ( */
            0x00, 0x41, 0x22, 0x1c, 0x00, /* ) */
            0x14, 0x08, 0x3e, 0x08, 0x14, /* // */
            0x08, 0x08, 0x3e, 0x08, 0x08, /* + */
            0x50, 0x30, 0x00, 0x00, 0x00, /* , */
            0x08, 0x08, 0x08, 0x08, 0x08, /* - */
            0x00, 0x60, 0x60, 0x00, 0x00, /* . */
            0x20, 0x10, 0x08, 0x04, 0x02, /* / */
            0x3e, 0x51, 0x49, 0x45, 0x3e, /* 0		0x30 */
            0x00, 0x42, 0x7f, 0x40, 0x00, /* 1 */
            0x42, 0x61, 0x51, 0x49, 0x46, /* 2 */
            0x21, 0x41, 0x45, 0x4b, 0x31, /* 3 */
            0x18, 0x14, 0x12, 0x7f, 0x10, /* 4 */
            0x27, 0x45, 0x45, 0x45, 0x39, /* 5 */
            0x3c, 0x4a, 0x49, 0x49, 0x30, /* 6 */
            0x01, 0x71, 0x09, 0x05, 0x03, /* 7 */
            0x36, 0x49, 0x49, 0x49, 0x36, /* 8 */
            0x06, 0x49, 0x49, 0x29, 0x1e, /* 9 */
            0x00, 0x36, 0x36, 0x00, 0x00, /* : */
            0x00, 0x56, 0x36, 0x00, 0x00, /* ; */
            0x08, 0x14, 0x22, 0x41, 0x00, /* < */
            0x14, 0x14, 0x14, 0x14, 0x14, /* = */
            0x00, 0x41, 0x22, 0x14, 0x08, /* > */
            0x02, 0x01, 0x51, 0x09, 0x06, /* ? */
            0x3e, 0x41, 0x5d, 0x55, 0x1e, /* @		0x40 */
            0x7e, 0x11, 0x11, 0x11, 0x7e, /* A */
            0x7f, 0x49, 0x49, 0x49, 0x36, /* B */
            0x3e, 0x41, 0x41, 0x41, 0x22, /* C */
            0x7f, 0x41, 0x41, 0x22, 0x1c, /* D */
            0x7f, 0x49, 0x49, 0x49, 0x41, /* E */
            0x7f, 0x09, 0x09, 0x09, 0x01, /* F */
            0x3e, 0x41, 0x49, 0x49, 0x7a, /* G */
            0x7f, 0x08, 0x08, 0x08, 0x7f, /* H */
            0x00, 0x41, 0x7f, 0x41, 0x00, /* I */
            0x20, 0x40, 0x41, 0x3f, 0x01, /* J */
            0x7f, 0x08, 0x14, 0x22, 0x41, /* K */
            0x7f, 0x40, 0x40, 0x40, 0x40, /* L */
            0x7f, 0x02, 0x0c, 0x02, 0x7f, /* M */
            0x7f, 0x04, 0x08, 0x10, 0x7f, /* N */
            0x3e, 0x41, 0x41, 0x41, 0x3e, /* O */
            0x7f, 0x09, 0x09, 0x09, 0x06, /* P		0x50 */
            0x3e, 0x41, 0x51, 0x21, 0x5e, /* Q */
            0x7f, 0x09, 0x19, 0x29, 0x46, /* R */
            0x26, 0x49, 0x49, 0x49, 0x32, /* S */
            0x01, 0x01, 0x7f, 0x01, 0x01, /* T */
            0x3f, 0x40, 0x40, 0x40, 0x3f, /* U */
            0x1f, 0x20, 0x40, 0x20, 0x1f, /* V */
            0x3f, 0x40, 0x38, 0x40, 0x3f, /* W */
            0x63, 0x14, 0x08, 0x14, 0x63, /* X */
            0x07, 0x08, 0x70, 0x08, 0x07, /* Y */
            0x61, 0x51, 0x49, 0x45, 0x43, /* Z */
            0x00, 0x7f, 0x41, 0x41, 0x00, /* [ */
            0x02, 0x04, 0x08, 0x10, 0x20, /* \ */
            0x00, 0x41, 0x41, 0x7f, 0x00, /* ] */
            0x04, 0x02, 0x01, 0x02, 0x04, /* ^ */
            0x40, 0x40, 0x40, 0x40, 0x40, /* _ */
            0x00, 0x00, 0x03, 0x05, 0x00, /* `		0x60 */
            0x20, 0x54, 0x54, 0x54, 0x78, /* a */
            0x7F, 0x44, 0x44, 0x44, 0x38, /* b */
            0x38, 0x44, 0x44, 0x44, 0x44, /* c */
            0x38, 0x44, 0x44, 0x44, 0x7f, /* d */
            0x38, 0x54, 0x54, 0x54, 0x18, /* e */
            0x04, 0x04, 0x7e, 0x05, 0x05, /* f */
            0x08, 0x54, 0x54, 0x54, 0x3c, /* g */
            0x7f, 0x08, 0x04, 0x04, 0x78, /* h */
            0x00, 0x44, 0x7d, 0x40, 0x00, /* i */
            0x20, 0x40, 0x44, 0x3d, 0x00, /* j */
            0x7f, 0x10, 0x28, 0x44, 0x00, /* k */
            0x00, 0x41, 0x7f, 0x40, 0x00, /* l */
            0x7c, 0x04, 0x7c, 0x04, 0x78, /* m */
            0x7c, 0x08, 0x04, 0x04, 0x78, /* n */
            0x38, 0x44, 0x44, 0x44, 0x38, /* o */
            0x7c, 0x14, 0x14, 0x14, 0x08, /* p		0x70 */
            0x08, 0x14, 0x14, 0x14, 0x7c, /* q */
            0x7c, 0x08, 0x04, 0x04, 0x00, /* r */
            0x48, 0x54, 0x54, 0x54, 0x24, /* s */
            0x04, 0x04, 0x3f, 0x44, 0x44, /* t */
            0x3c, 0x40, 0x40, 0x20, 0x7c, /* u */
            0x1c, 0x20, 0x40, 0x20, 0x1c, /* v */
            0x3c, 0x40, 0x30, 0x40, 0x3c, /* w */
            0x44, 0x28, 0x10, 0x28, 0x44, /* x */
            0x0c, 0x50, 0x50, 0x50, 0x3c, /* y */
            0x44, 0x64, 0x54, 0x4c, 0x44, /* z */
            0x08, 0x36, 0x41, 0x41, 0x00, /* { */
            0x00, 0x00, 0x77, 0x00, 0x00, /* | */
            0x00, 0x41, 0x41, 0x36, 0x08, /* } */
            0x08, 0x08, 0x2a, 0x1c, 0x08  /* ~ */
        };

        private void DrawLetter(int x, int y, char letter, Color color, int scaleFactor)
        {
            var index = 5 * (letter - 32);
            var upper = (byte)(color.As565 >> 8);
            var lower = (byte)(color.As565 >> 0);
            var characterBuffer = scaleFactor == 1 ? _characterBuffer1 : (scaleFactor == 2 ? _characterBuffer2 : _characterBuffer4);

            var i = 0;

            for (var j = 1; j <= 64; j *= 2)
            {
                for (var k = 0; k < scaleFactor; k++)
                {
                    for (var l = 0; l < 5; l++)
                    {
                        for (var m = 0; m < scaleFactor; m++)
                        {
                            var show = (Font[index + l] & j) != 0;

                            characterBuffer[i++] = show ? upper : (byte)0x00;
                            characterBuffer[i++] = show ? lower : (byte)0x00;
                        }
                    }
                }
            }

            SetClip(x, y, 5 * scaleFactor, 8 * scaleFactor);
            DrawImage(characterBuffer);
        }

        /// <summary>
        /// Draws a letter at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="letter">The letter to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawLetter(int x, int y, char letter, Color color)
        {
            if (letter > 126 || letter < 32) throw new ArgumentOutOfRangeException(nameof(letter), "This letter cannot be drawn.");
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawLetter(x, y, letter, color, 1);
        }

        /// <summary>
        /// Draws a large letter at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="letter">The letter to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawLargeLetter(int x, int y, char letter, Color color)
        {
            if (letter > 126 || letter < 32) throw new ArgumentOutOfRangeException(nameof(letter), "This letter cannot be drawn.");
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawLetter(x, y, letter, color, 2);
        }

        /// <summary>
        /// Draws an extra large letter at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="letter">The letter to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawExtraLargeLetter(int x, int y, char letter, Color color)
        {
            if (letter > 126 || letter < 32) throw new ArgumentOutOfRangeException(nameof(letter), "This letter cannot be drawn.");
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawLetter(x, y, letter, color, 4);
        }

        /// <summary>
        /// Draws text at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="text">The string to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawText(int x, int y, string text, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (text == null) throw new ArgumentNullException("data");

            for (var i = 0; i < text.Length; i++)
                DrawLetter(x + i * 6, y, text[i], color, 1);
        }

        /// <summary>
        /// Draws large text at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="text">The string to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawLargeText(int x, int y, string text, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (text == null) throw new ArgumentNullException("data");

            for (var i = 0; i < text.Length; i++)
                DrawLetter(x + i * 6 * 2, y, text[i], color, 2);
        }

        /// <summary>
        /// Draws extra large text at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="text">The string to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawExtraLargeText(int x, int y, string text, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (text == null) throw new ArgumentNullException("data");

            for (var i = 0; i < text.Length; i++)
                DrawLetter(x + i * 6 * 4, y, text[i], color, 4);
        }

        /// <summary>
        /// Draws a number at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="number">The number to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawNumber(int x, int y, double number, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawText(x, y, number.ToString("N2"), color);
        }

        /// <summary>
        /// Draws a large number at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="number">The number to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawLargeNumber(int x, int y, double number, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawLargeText(x, y, number.ToString("N2"), color);
        }

        /// <summary>
        /// Draws an extra large number at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="number">The number to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawExtraLargeNumber(int x, int y, double number, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawExtraLargeText(x, y, number.ToString("N2"), color);
        }

        /// <summary>
        /// Draws a number at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="number">The number to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawNumber(int x, int y, long number, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawText(x, y, number.ToString("N0"), color);
        }

        /// <summary>
        /// Draws a large number at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="number">The number to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawLargeNumber(int x, int y, long number, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawLargeText(x, y, number.ToString("N0"), color);
        }

        /// <summary>
        /// Draws an extra large number at the given location.
        /// </summary>
        /// <param name="x">The x coordinate to draw at.</param>
        /// <param name="y">The y coordinate to draw at.</param>
        /// <param name="number">The number to draw.</param>
        /// <param name="color">The color to use.</param>
        public void DrawExtraLargeNumber(int x, int y, long number, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");

            DrawExtraLargeText(x, y, number.ToString("N0"), color);
        }
    }

    public class Color
    {
        /// <summary>
        /// The amount of red.
        /// </summary>
        public byte R { get; set; }

        /// <summary>
        /// The amount of green.
        /// </summary>
        public byte G { get; set; }

        /// <summary>
        /// The amount of blue.
        /// </summary>
        public byte B { get; set; }

        /// <summary>
        /// The color in 565 format.
        /// </summary>
        public ushort As565
        {
            get
            {
                return (ushort)(((R & 0x1F) << 11) | ((G & 0x3F) << 5) | (B & 0x1F));
            }
        }

        /// <summary>
        /// Constructs a new instance with the given levels.
        /// </summary>
        public Color()
            : this(0, 0, 0)
        {

        }

        /// <summary>
        /// Constructs a new instance with the given levels.
        /// </summary>
        /// <param name="red">The amount of red.</param>
        /// <param name="green">The amount of green.</param>
        /// <param name="blue">The amount of blue.</param>
        public Color(byte red, byte green, byte blue)
        {
            R = red;
            G = green;
            B = blue;
        }

        /// <summary>
        /// A predefined color for black.
        /// </summary>
        public static Color Black = new Color(0, 0, 0);
        /// <summary>
        /// A predefined color for white.
        /// </summary>
        public static Color White = new Color(255, 255, 255);
        /// <summary>
        /// A predefined color for red.
        /// </summary>
        public static Color Red = new Color(255, 0, 0);
        /// <summary>
        /// A predefined color for green.
        /// </summary>
        public static Color Green = new Color(0, 255, 0);
        /// <summary>
        /// A predefined color for blue.
        /// </summary>
        public static Color Blue = new Color(0, 0, 255);
        /// <summary>
        /// A predefined color for yellow.
        /// </summary>
        public static Color Yellow = new Color(255, 255, 0);
        /// <summary>
        /// A predefined color for cyan.
        /// </summary>
        public static Color Cyan = new Color(0, 255, 255);
        /// <summary>
        /// A predefined color for magenta.
        /// </summary>
        public static Color Magenta = new Color(255, 0, 255);
    }

    /// <summary>
    /// Represents an image that can be reused.
    /// </summary>
    public class Image
    {
        /// <summary>
        /// Width of image
        /// </summary>
        public int Width { get; private set; }

        /// <summary>
        /// Height of image
        /// </summary>
        public int Height { get; private set; }

        /// <summary>
        /// Data of each pixel of image
        /// </summary>
        public byte[] Pixels { get; private set; }

        /// <summary>
        /// Constructs a new image with the given dimensions.
        /// </summary>
        /// <param name="width">The image width.</param>
        /// <param name="height">The image height.</param>
        public Image(int width, int height)
        {
            if (width < 0) throw new ArgumentOutOfRangeException(nameof(width), "width must not be negative.");
            if (height < 0) throw new ArgumentOutOfRangeException(nameof(height), "height must not be negative.");

            Width = width;
            Height = height;
            Pixels = new byte[width * height * 2];
        }

        /// <summary>
        /// Sets the given pixel to the given color.
        /// </summary>
        /// <param name="x">The x coordinate of the pixel to set.</param>
        /// <param name="y">The y coordinate of the pixel to set.</param>
        /// <param name="color">The color to set the pixel to.</param>
        public void SetPixel(int x, int y, Color color)
        {
            if (x < 0) throw new ArgumentOutOfRangeException(nameof(x), "x must not be negative.");
            if (y < 0) throw new ArgumentOutOfRangeException(nameof(y), "y must not be negative.");
            if (x > Width) throw new ArgumentOutOfRangeException(nameof(x), "x must not exceed Width.");
            if (y > Height) throw new ArgumentOutOfRangeException(nameof(y), "y must not exceed Height.");

            Pixels[(x * Width + y) * 2 + 0] = (byte)(color.As565 >> 8);
            Pixels[(x * Width + y) * 2 + 1] = (byte)(color.As565 >> 0);
        }
    }
}