Windows IoT and N18Display

I have succeeded to clear screen !!! ;D
Even to draw a blue pixel … But I must dive in datasheet to understand color bytes.

Mistake was in that function:


        private void WriteData(ushort[] data)
        {
            _cdIo.Write(true);
            foreach (var us in data)
            {
                _spiDevice.Write(BitConverter.GetBytes(us).Reverse().ToArray());
            }
        }

In didn’t have reversed order of byte so coordinates was incorrect. But without analyzer is would be difficult to find it !!!

Please, can you explain a little bit more what you mean. Is there a difference between NETMF and Ras Pi how bytes are sent over SPI MOSI, LSB first or MSB first? Or what do you mean?

It concerns just sending ushort. If ushort contains 0x003f, Bitconverter.getbytes seems convert it to 0x3f, 0x00. So if I reverse order, all was ok: 0x00, 0x3f. I’m using FEZ Cream finally so it’s NetMF.

1 Like

Ah, that is the ENDIAN rule. Each micro and/or OS has it’s own way of storing the data, little endian or big endian. In your case, it was the BYTE order that was wrong. Well spotted and you are correct, a logic analyser is something software engineers working on embedded designs should not be without :slight_smile:

Progress have been done … See picture !


        private void _button_Pressed(Button sender, object args)
        {
            _bLed = !_bLed;
            _button.SetLed(_bLed);
            N18Font font = new N18Font();
            _display.DrawText("Hello World !", font, 10, 10, 0xff, 0xff, 0x00);
            _display.DrawRect(50, 50, 25, 25, 0xff, 0xff, 0x00);
            _display.DrawRect(100, 50, 25, 25, 0xff, 0x00, 0xff);
            _display.DrawRect(50, 100, 25, 25, 0x00, 0xff, 0xff);
            _display.DrawRect(100, 100, 25, 25, 0xff, 0xff, 0xff);
        }

3 Likes

Driver goes on ! What is missing:
[ul]display a bitmap,
display line,
display empty rectangle,
display circle.
[/ul]

In codeshare: [url]https://www.ghielectronics.com/community/codeshare/entry/1093[/url]
As codeshare has disappeared (hidden should I say), here is the code:


using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Spi;
using GHIElectronics.UWP.GadgeteerCore;
using GHIElectronics.UWP.GadgeteerCore.SocketInterfaces;
using SpiDevice = GHIElectronics.UWP.GadgeteerCore.SocketInterfaces.SpiDevice;

//using SpiDevice = GHIElectronics.UWP.GadgeteerCore.SocketInterfaces.SpiDevice;

namespace Bauland
{
    /// <summary>
    /// Class to manage N18display
    /// </summary>
    public class N18Display : Module
    {
        /// <summary>
        /// Module's name
        /// </summary>
        public override string Name => "N18Display";

        /// <summary>
        /// Manufacturer's module
        /// </summary>
        public override string Manufacturer => "GHI";

        private SpiDevice _spiDevice;
        private DigitalIO _resetIo; // Hardware reset
        private DigitalIO _backlightIo; // backlight control
        private DigitalIO _cdIo; // command: false, data: true
        private byte[] _data;
        private ushort[] _ushortData;
        private DisplayOrientation _orientation;
        private bool _isBgr;

        /// <summary>
        /// Get or set display orientation. Screen is not clear, so previous text remains on screen.
        /// </summary>
        public DisplayOrientation Orientation
        {
            get { return _orientation; }
            set
            {
                _orientation = value;
                SetFormat();
            }
        }

        /// <summary>
        /// If channel red and blue are swaped for this display, then call one.
        /// </summary>
        public void SwapRedBlueChannels()
        {
            _isBgr = !_isBgr;
            SetFormat();
        }

        private void SetFormat()
        {
            var bgr = (_isBgr ? MADCTL_BGR : 0);
            WriteCommand(MADCTL);
            switch (_orientation)
            {
                case DisplayOrientation.Normal:
                    WriteData((byte)(MADCTL_MX | MADCTL_MY | bgr));
                    break;
                case DisplayOrientation.Cw90:
                    WriteData((byte)(MADCTL_MV | MADCTL_MY | bgr));
                    break;
                case DisplayOrientation.UpsideDown:
                    WriteData((byte)(bgr));
                    break;
                case DisplayOrientation.Ccw90:
                    WriteData((byte)(MADCTL_MV | MADCTL_MX | bgr));
                    break;
            }
        }

        #region Command for ST7735

        private byte CASET = 0x2A;
        private byte RASET = 0x2B;
        private byte RAMWR = 0x2C;
        private byte DISPON = 0x29;
        private byte SLPOUT = 0x11;
        private byte FRMCTR1 = 0xB1;
        private byte FRMCTR2 = 0xB2;
        private byte FRMCTR3 = 0xB3;
        private byte INVCTR = 0xB4;
        private byte PWCTR1 = 0xC0;
        private byte PWCTR2 = 0xC1;
        private byte PWCTR3 = 0xC2;
        private byte PWCTR4 = 0xC3;
        private byte PWCTR5 = 0xC4;
        private byte VMCTR1 = 0xC5;
        private byte GMCTRP1 = 0xE0;
        private byte GMCTRN1 = 0xE1;
        private byte COLMOD = 0x3A;
        private byte MADCTL = 0x36;
        private byte MADCTL_BGR = 0x08;
        private byte MADCTL_MX = 0x40;
        private byte MADCTL_MY = 0x80;
        private byte MADCTL_MV = 0x20;

        public N18Display()
        {
            _isBgr = true;
        }

        #endregion

        /// <summary>
        /// Initialize module and connect to mainboard
        /// </summary>
        /// <param name="parentSocket">Socket of mainboard on which module is connected</param>
        /// <returns>Task</returns>
        protected override async Task Initialize(ISocket parentSocket)
        {
            parentSocket.EnsureTypeIsSupported(SocketType.S);
            _data = null;
            _spiDevice =
                await
                    parentSocket.CreateSpiDeviceAsync(new SpiConnectionSettings(0)
                    {
                        Mode = SpiMode.Mode0,
                        ClockFrequency = 10000000,
                    });
            _resetIo = await parentSocket.CreateDigitalIOAsync(SocketPinNumber.Three, false);
            _backlightIo = await parentSocket.CreateDigitalIOAsync(SocketPinNumber.Four, true);
            _cdIo = await parentSocket.CreateDigitalIOAsync(SocketPinNumber.Five, false);
            _data = new byte[1];
            _ushortData = new ushort[2];
            await HardReset(); // Hard reset
        }

        /// <summary>
        /// Hard reset display.
        /// </summary>
        /// <returns>Task object</returns>
        public async Task HardReset()
        {
            await Reset();
            await ConfigureDisplay();
            Clear();
        }

        private async Task ConfigureDisplay()
        {
            // ST7735R or ST7735 ???
            WriteCommand(SLPOUT); // Exit sleep mode
            await Task.Delay(120);
            WriteCommand(FRMCTR1);
            WriteData(0x01);
            WriteData(0x2C);
            WriteData(0x2D);
            WriteCommand(FRMCTR2);
            WriteData(0x01);
            WriteData(0x2C);
            WriteData(0x2D);
            WriteCommand(FRMCTR3);
            WriteData(0x01);
            WriteData(0x2C);
            WriteData(0x2D);
            WriteData(0x01);
            WriteData(0x2C);
            WriteData(0x2D);
            WriteCommand(INVCTR); // Column inversion
            WriteData(0x07);

            // Power Sequence
            WriteCommand(PWCTR1);
            WriteData(0xA2);
            WriteData(0x02);
            WriteData(0x84);
            WriteCommand(PWCTR2);
            WriteData(0xc5);
            WriteCommand(PWCTR3);
            WriteData(0x0a);
            WriteData(0x00);
            WriteCommand(PWCTR4);
            WriteData(0x8a);
            WriteData(0x2a);
            WriteCommand(PWCTR5);
            WriteData(0x8a);
            WriteData(0xee);

            // VCOM
            WriteCommand(VMCTR1);
            WriteData(0x0e);

            // Mode address
            SetFormat();

            // Gamma Sequence
            WriteCommand(GMCTRP1);
            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(GMCTRN1);
            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(CASET);
            WriteData(0x00);
            WriteData(0x00);
            WriteData(0x00);
            WriteData(0x7f);
            WriteCommand(RASET);
            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(COLMOD); // 65k mode
            WriteData(0x55);
            WriteCommand(DISPON); // Display on
        }

        /// <summary>
        /// Get or set backlight status. Must be true to see what is displayed.
        /// </summary>
        public bool Backlight
        {
            get { return _backlightIo.Read(); }
            set { _backlightIo.Write(value); }
        }

        /// <summary>
        /// Soft reset
        /// </summary>
        /// <returns>Task Object</returns>
        public async Task Reset()
        {
            _resetIo.Write(false);
            await Task.Delay(150);
            _resetIo.Write(true);
        }

        private void WriteCommand(byte command)
        {
            _data[0] = command;
            _cdIo.Write(false);
            _spiDevice.Write(_data);
        }

        private void WriteData(byte[] data)
        {
            _cdIo.Write(true);
            _spiDevice.Write(data);
        }

        private void WriteData(byte data)
        {
            _data[0] = data;
            WriteData(_data);
        }

        private void WriteData(ushort[] data)
        {
            _cdIo.Write(true);
            foreach (var us in data)
            {
                _spiDevice.Write(BitConverter.GetBytes(us).Reverse().ToArray());
            }
        }

        /// <summary>
        /// Clear Display
        /// </summary>
        public void Clear()
        {
            var data = new byte[64 * 80 * 2];
            DrawRaw(data, 0, 0, 64, 80);
            DrawRaw(data, 64, 0, 64, 80);
            DrawRaw(data, 0, 80, 64, 80);
            DrawRaw(data, 64, 80, 64, 80);
        }

        private void DrawRaw(byte[] data, int x, int y, int width, int height)
        {
            SetClippingArea(x, y, width - 1, height - 1);
            WriteCommand(RAMWR);
            WriteData(data);
        }

        private void SetClippingArea(int x, int y, int width, int height)
        {
            _ushortData[0] = (ushort)x;
            _ushortData[1] = (ushort)(x + width);
            WriteCommand(CASET);
            WriteData(_ushortData);
            _ushortData[0] = (ushort)y;
            _ushortData[1] = (ushort)(y + height);
            WriteCommand(RASET);
            WriteData(_ushortData);
        }

        /// <summary>
        /// Draw a pixel on display
        /// </summary>
        /// <param name="x">x coordinate of pixel</param>
        /// <param name="y">y coordinate of pixel</param>
        /// <param name="color">color of pixel</param>
        public void DrawPixel(int x, int y, Color color)
        {
            byte[] a = color.To565ByteArray();
            DrawRaw(a, x, y, 1, 1);
        }

        /// <summary>
        /// Draw a pixel on display
        /// </summary>
        /// <param name="x">x coordinate of pixel</param>
        /// <param name="y">y coordinate of pixel</param>
        /// <param name="r">red channel value</param>
        /// <param name="g">green channel value</param>
        /// <param name="b">blue channel value</param>
        public void DrawPixel(int x, int y, byte r, byte g, byte b)
        {
            byte[] a = ToRGB565(r, g, b);
            DrawRaw(a, x, y, 1, 1);
        }

        /// <summary>
        /// Draw a fill rectangle on display
        /// </summary>
        /// <param name="x">x coordinate of pixel</param>
        /// <param name="y">y coordinate of pixel</param>
        /// <param name="width">width of rectangle</param>
        /// <param name="height">height of rectangle</param>
        /// <param name="color">color of rectangle</param>
        public void DrawFillRect(int x, int y, int width, int height, Color color)
        {
            // Prepare data
            byte[] data = new byte[width * height * 2];
            byte[] a = color.To565ByteArray();
            for (int i = 0; i < width * height; i++)
            {
                data[i * 2] = a[0];
                data[i * 2 + 1] = a[1];
            }
            DrawRaw(data, x, y, width, height);
        }

        /// <summary>
        /// Draw a fill rectangle on display
        /// </summary>
        /// <param name="x">x coordinate of pixel</param>
        /// <param name="y">y coordinate of pixel</param>
        /// <param name="width">width of rectangle</param>
        /// <param name="height">height of rectangle</param>
        /// <param name="r">red channel value</param>
        /// <param name="g">green channel value</param>
        /// <param name="b">blue channel value</param>
        public void DrawFillRect(int x, int y, int width, int height, byte r, byte g, byte b)
        {
            // Prepare data
            byte[] data = new byte[width * height * 2];
            byte[] a = ToRGB565(r, g, b);
            for (int i = 0; i < width * height; i++)
            {
                data[i * 2] = a[0];
                data[i * 2 + 1] = a[1];
            }
            DrawRaw(data, x, y, width, height);
        }

        private byte[] ToRGB565(byte r, byte g, byte b)
        {
            byte[] arrayBytes = new byte[2];
            r = (byte)(r & 0x1f);
            g = (byte)(g & 0x3f);
            b = (byte)(b & 0x1f);

            arrayBytes[0] = (byte)((b << 3) + (g >> 3));
            arrayBytes[1] = (byte)((g << 5) + r);
            return arrayBytes;
        }

        /// <summary>
        /// Draw a single character on display
        /// </summary>
        /// <param name="c">Character to display</param>
        /// <param name="font">Font used to display character</param>
        /// <param name="x">x coordinate on screen</param>
        /// <param name="y">y coordinate on screen</param>
        /// <param name="color">Color of character</param>
        public void DrawChar(char c, Font font, int x, int y, Color color)
        {
            var letter = font.GetLetter(c);
            int height = 8;
            int width = letter.Length;
            byte[] abcolor = color.To565ByteArray();
            byte[] data = new byte[height * width * 2];
            byte mask = 0x01;
            bool bit;
            for (int j = 0; j < 8; j++)
            {
                for (int i = 0; i < letter.Length; i++)
                {
                    bit = (letter[i] & mask) == mask;
                    if (bit)
                    {
                        data[2 * (i + j * letter.Length)] = abcolor[0];
                        data[2 * (i + j * letter.Length) + 1] = abcolor[1];
                    }
                }
                mask = (byte)(mask << 1);
            }
            DrawRaw(data, x, y, width, height);
        }

        /// <summary>
        /// Draw a single character on display
        /// </summary>
        /// <param name="c">Character to display</param>
        /// <param name="font">Font used to display character</param>
        /// <param name="x">x coordinate on screen</param>
        /// <param name="y">y coordinate on screen</param>
        /// <param name="r">red channel value</param>
        /// <param name="g">green channel value</param>
        /// <param name="b">blue channel value</param>
        public void DrawChar(char c, Font font, int x, int y, byte r, byte g, byte b)
        {
            var letter = font.GetLetter(c);
            int height = 8;
            int width = letter.Length;
            byte[] color = ToRGB565(r, g, b);
            byte[] data = new byte[height * width * 2];
            byte mask = 0x01;
            bool bit;
            for (int j = 0; j < 8; j++)
            {
                for (int i = 0; i < letter.Length; i++)
                {
                    bit = (letter[i] & mask) == mask;
                    if (bit)
                    {
                        data[2 * (i + j * letter.Length)] = color[0];
                        data[2 * (i + j * letter.Length) + 1] = color[1];
                    }
                }
                mask = (byte)(mask << 1);
            }
            DrawRaw(data, x, y, width, height);
        }

        /// <summary>
        /// Draw a text on display
        /// </summary>
        /// <param name="text">Text to display</param>
        /// <param name="font">Font used to display text</param>
        /// <param name="x">x coordinate on screen</param>
        /// <param name="y">y coordinate on screen</param>
        /// <param name="color">Color of text to display</param>
        public void DrawText(string text, Font font, int x, int y, Color color)
        {
            int fontWidth = font.Width;
            int index = 0;
            foreach (char c in text)
            {
                DrawChar(c, font, x + index * (fontWidth + 1), y, color);
                index++;
            }
        }

        /// <summary>
        /// Draw a text on display
        /// </summary>
        /// <param name="text">Text to display</param>
        /// <param name="font">Font used to display text</param>
        /// <param name="x">x coordinate on screen</param>
        /// <param name="y">y coordinate on screen</param>
        /// <param name="r">red channel value</param>
        /// <param name="g">green channel value</param>
        /// <param name="b">blue channel value</param>
        public void DrawText(string text, Font font, int x, int y, byte r, byte g, byte b)
        {
            int fontWidth = font.Width;
            int index = 0;
            foreach (char c in text)
            {
                DrawChar(c, font, x + index * (fontWidth + 1), y, r, g, b);
                index++;
            }
        }

        /// <summary>
        /// Orientation value of screen
        /// </summary>
        public enum DisplayOrientation
        {
            Normal,
            Cw90,
            UpsideDown,
            Ccw90
        }
    }
}

3 Likes