Adafruit Mini TFT with Joystick Featherwing Driver

For anyone interested in this piece of hardware here is some code that might help.

Driver Code:

using GHIElectronics.TinyCLR.Devices.Gpio;
using GHIElectronics.TinyCLR.Devices.I2c;
using GHIElectronics.TinyCLR.Devices.Spi;
using System;
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace Sample
{


    public enum ST7735CommandId : byte
    {
        //System
        NOP = 0x00,
        SWRESET = 0x01,
        RDDID = 0x04,
        RDDST = 0x09,
        RDDPM = 0x0A,
        RDDMADCTL = 0x0B,
        RDDCOLMOD = 0x0C,
        RDDIM = 0x0D,
        RDDSM = 0x0E,
        SLPIN = 0x10,
        SLPOUT = 0x11,
        PTLON = 0x12,
        NORON = 0x13,
        INVOFF = 0x20,
        INVON = 0x21,
        GAMSET = 0x26,
        DISPOFF = 0x28,
        DISPON = 0x29,
        CASET = 0x2A,
        RASET = 0x2B,
        RAMWR = 0x2C,
        RAMRD = 0x2E,
        PTLAR = 0x30,
        TEOFF = 0x34,
        TEON = 0x35,
        MADCTL = 0x36,
        IDMOFF = 0x38,
        IDMON = 0x39,
        COLMOD = 0x3A,
        RDID1 = 0xDA,
        RDID2 = 0xDB,
        RDID3 = 0xDC,

        //Panel
        FRMCTR1 = 0xB1,
        FRMCTR2 = 0xB2,
        FRMCTR3 = 0xB3,
        INVCTR = 0xB4,
        DISSET5 = 0xB6,
        PWCTR1 = 0xC0,
        PWCTR2 = 0xC1,
        PWCTR3 = 0xC2,
        PWCTR4 = 0xC3,
        PWCTR5 = 0xC4,
        VMCTR1 = 0xC5,
        VMOFCTR = 0xC7,
        WRID2 = 0xD1,
        WRID3 = 0xD2,
        NVCTR1 = 0xD9,
        NVCTR2 = 0xDE,
        NVCTR3 = 0xDF,
        GAMCTRP1 = 0xE0,
        GAMCTRN1 = 0xE1,
    }
    public enum Buttons
    {

        BUTTON_UP_PIN = 1 << 2,
        BUTTON_LEFT_PIN = 1 << 3,
        BUTTON_DOWN_PIN = 1 << 4,

        BUTTON_RIGHT_PIN = 1 << 7,
        BUTTON_B_PIN = 1 << 9,
        BUTTON_A_PIN = 1 << 10,
        BUTTON_SELECT_PIN = 1 << 11,
        ALL = (1 << 2) | (1 << 3) | (1 << 4) | (1 << 7) | (1 << 9) | (1 << 10) | (1 << 11)
    }
    //Used for raising button events
    public delegate void ButtonStateChangedEventHandler(Buttons button, bool newState);
    public class AdafruitMiniTFTWithJoystickFeatherwing
    {
        private readonly byte[] buffer1 = new byte[1];
        private readonly byte[] buffer4 = new byte[4];

        private readonly SpiDevice spi;
        private readonly I2cDevice _i2cDevice;
        private readonly GpioPin control;


        const byte ST77XX_MADCTL_MY = 0x80;
        const byte ST77XX_MADCTL_MX = 0x40;
        const byte ST77XX_MADCTL_MV = 0x20;
        const byte ST77XX_MADCTL_ML = 0x10;
        const byte ST77XX_MADCTL_RGB = 0x00;


        const byte SEESAW_STATUS_BASE = 0x00;
        const byte SEESAW_STATUS_SWRST = 0x7F;
        const byte SEESAW_GPIO_BASE = 0x01;
        const byte SEESAW_TIMER_BASE = 0x08;
        /* 
         * GPIO module function addres registers
         */
        const byte SEESAW_GPIO_DIRSET_BULK = 0x02;
        const byte SEESAW_GPIO_DIRCLR_BULK = 0x03;
        const byte SEESAW_GPIO_BULK = 0x04;
        const byte SEESAW_GPIO_BULK_SET = 0x05;
        const byte SEESAW_GPIO_BULK_CLR = 0x06;
        const byte SEESAW_GPIO_PULLENSET = 0x0B;
        /*
         * status module function addres registers
         */
        const byte SEESAW_STATUS_HW_ID = 0x01;
        const byte SEESAW_TIMER_PWM = 0x01;
        const byte SEESAW_HW_ID_CODE = 0x55;
        int _colstart = 24;
        int _rowstart = 0;

        int MaxWidth = 80;
        int MaxHeight = 160;
        public int Height => 80;
        public int Width => 160;

        //Seesaw reset pin 
        const int _resetPin = 1 << 8;
        int _lastPressed = 0;
        private int _xstart;
        private int _ystart;
        private int _width;
        private int _height;

        public event ButtonStateChangedEventHandler OnButtonStateChanged;

        public AdafruitMiniTFTWithJoystickFeatherwing(I2cController controller, SpiController spiController, GpioPin chipSelect, GpioPin control, int debounceMs = 50)
        {
            spi = spiController.GetDevice(new SpiConnectionSettings
            {

                Mode = SpiMode.Mode3,
                ClockFrequency = 12_000_000,
                ChipSelectType = SpiChipSelectType.Gpio,
                ChipSelectLine = chipSelect
            });
            _i2cDevice = controller.GetDevice(new I2cConnectionSettings(0x5E));
            this.control = control;
            this.control.SetDriveMode(GpioPinDriveMode.Output);

            Initialize();


            //Thread used to read the seesaw pin values and raise event as needed
            new Thread(() =>
            {
                var buttons = (int)(Buttons.BUTTON_UP_PIN | Buttons.BUTTON_DOWN_PIN | Buttons.BUTTON_LEFT_PIN | Buttons.BUTTON_RIGHT_PIN | Buttons.BUTTON_SELECT_PIN | Buttons.BUTTON_A_PIN | Buttons.BUTTON_B_PIN);
                if (debounceMs < 20)
                    debounceMs = 20;
                while (true)
                {

                    var buffer = new byte[8];
                    _i2cDevice.Write(new byte[] { SEESAW_GPIO_BASE, SEESAW_GPIO_BULK });
                    _i2cDevice.Read(buffer);

                    int ret = (buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3];
                    var active = (int)buttons - (ret & buttons);
                    if (active != _lastPressed)
                    {
                        var isPressed = active > _lastPressed;

                        var diff = isPressed ? active - _lastPressed : _lastPressed - active;

                        if (diff >= (int)Buttons.BUTTON_SELECT_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_SELECT_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_SELECT_PIN;
                        }
                        if (diff >= (int)Buttons.BUTTON_A_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_A_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_A_PIN;
                        }
                        if (diff >= (int)Buttons.BUTTON_B_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_B_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_B_PIN;
                        }
                        if (diff >= (int)Buttons.BUTTON_RIGHT_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_RIGHT_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_RIGHT_PIN;
                        }
                        if (diff >= (int)Buttons.BUTTON_DOWN_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_DOWN_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_DOWN_PIN;
                        }
                        if (diff >= (int)Buttons.BUTTON_LEFT_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_LEFT_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_LEFT_PIN;
                        }
                        if (diff >= (int)Buttons.BUTTON_UP_PIN)
                        {
                            OnButtonStateChanged?.Invoke(Buttons.BUTTON_UP_PIN, isPressed);
                            diff -= (int)Buttons.BUTTON_UP_PIN;
                        }


                    }
                    _lastPressed = active;

                    Thread.Sleep(debounceMs);
                }

            }).Start();
        }



        private void Initialize()
        {
            #region Seesaw initialization


            _i2cDevice.Write(new byte[] { SEESAW_STATUS_BASE, SEESAW_STATUS_SWRST, 0xFF });
            Thread.Sleep(1000);
            var buffer = new byte[1];
            _i2cDevice.Write(new byte[] { SEESAW_STATUS_BASE, SEESAW_STATUS_HW_ID });
            _i2cDevice.Read(buffer);

            if (buffer[0] != SEESAW_HW_ID_CODE)
            {
                throw new Exception("Did not find device");
            }

            var buttons = Buttons.BUTTON_UP_PIN | Buttons.BUTTON_DOWN_PIN | Buttons.BUTTON_LEFT_PIN | Buttons.BUTTON_RIGHT_PIN | Buttons.BUTTON_SELECT_PIN | Buttons.BUTTON_A_PIN | Buttons.BUTTON_B_PIN;

            WritePinMode((int)buttons, 3);
            OnButtonStateChanged?.Invoke(Buttons.ALL, false);
            Thread.Sleep(100);
            WritePinMode(_resetPin, 1);
            #endregion

            #region ST7735 initialization

            //Reset ST7735
            DigitalWrite(_resetPin, true);
            Thread.Sleep(50);
            DigitalWrite(_resetPin, false);
            Thread.Sleep(50);
            DigitalWrite(_resetPin, true);
           


            SendCommand(ST7735CommandId.SWRESET);
            Thread.Sleep(120);

            SendCommand(ST7735CommandId.SLPOUT);
            Thread.Sleep(120);

            SendCommand(ST7735CommandId.FRMCTR1);
            SendData(0x01);
            SendData(0x2C);
            SendData(0x2D);

            SendCommand(ST7735CommandId.FRMCTR2);
            SendData(0x01);
            SendData(0x2C);
            SendData(0x2D);

            SendCommand(ST7735CommandId.FRMCTR3);
            SendData(0x01);
            SendData(0x2C);
            SendData(0x2D);
            SendData(0x01);
            SendData(0x2C);
            SendData(0x2D);

            SendCommand(ST7735CommandId.INVCTR);
            SendData(0x07);

            SendCommand(ST7735CommandId.PWCTR1);
            SendData(0xA2);
            SendData(0x02);
            SendData(0x84);

            SendCommand(ST7735CommandId.PWCTR2);
            SendData(0xC5);

            SendCommand(ST7735CommandId.PWCTR3);
            SendData(0x0A);
            SendData(0x00);

            SendCommand(ST7735CommandId.PWCTR4);
            SendData(0x8A);
            SendData(0x2A);

            SendCommand(ST7735CommandId.PWCTR5);
            SendData(0x8A);
            SendData(0xEE);

            SendCommand(ST7735CommandId.COLMOD);
            SendData(0x05);

            SendCommand(ST7735CommandId.VMCTR1);
            SendData(0x0E);

            SendCommand(ST7735CommandId.GAMCTRP1);
            this.SendData(new byte[] { 0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10 });


            SendCommand(ST7735CommandId.GAMCTRN1);
            this.SendData(new byte[] { 0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10 });



            SendCommand(ST7735CommandId.NORON);
            Thread.Sleep(10);

            
            SendCommand(ST7735CommandId.MADCTL);
            SendData(0xC0);

            SetRotation(3);

            SetDrawWindow(0, 0, _width, _height);

            SetBacklightIntensityPercentage(100);
            #endregion

        }

        public void Dispose()
        {
            spi.Dispose();
            control.Dispose();

        }

        public void Enable() => SendCommand(ST7735CommandId.DISPON);
        public void Disable() => SendCommand(ST7735CommandId.DISPOFF);

        private void SendCommand(ST7735CommandId command)
        {
            buffer1[0] = (byte)command;
            control.Write(GpioPinValue.Low);
            spi.Write(buffer1);
        }

        private void SendData(byte data)
        {
            buffer1[0] = data;
            control.Write(GpioPinValue.High);
            spi.Write(buffer1);
        }

        private void SendData(byte[] data)
        {
            control.Write(GpioPinValue.High);
            spi.Write(data);
        }


        void SetRotation(byte rotate)
        {
            byte madctl;
            switch (rotate)
            {

                case 1:
                    madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
                    _width = MaxHeight;
                    _height = MaxWidth;
                    _ystart = _colstart;
                    _xstart = _rowstart;
                    break;
                case 2:
                    madctl = ST77XX_MADCTL_RGB;
                    _height = MaxHeight;
                    _width = MaxWidth;
                    _xstart = _colstart;
                    _ystart = _rowstart;
                    break;
                case 3:
                    madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB;
                    _width = MaxHeight;
                    _height = MaxWidth;
                    _ystart = _colstart;
                    _xstart = _rowstart;
                    break;
                default:
                    madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB;
                    _height = MaxHeight;
                    _width = MaxWidth;
                    _xstart = _colstart;
                    _ystart = _rowstart;
                    break;
            }

            SendCommand(ST7735CommandId.MADCTL);
            SendData(madctl);

        }

        private void SetDrawWindow(int x, int y, int width, int height)
        {
            y += _ystart;
            x += _xstart;

            buffer4[1] = (byte)x;
            buffer4[3] = (byte)(x + width - 1);
            SendCommand(ST7735CommandId.CASET);
            SendData(buffer4);

            buffer4[1] = (byte)y;
            buffer4[3] = (byte)(y + height - 1);
            SendCommand(ST7735CommandId.RASET);
            SendData(buffer4);
        }

        private void SendDrawCommand()
        {
            SendCommand(ST7735CommandId.RAMWR);
            control.Write(GpioPinValue.High);
        }

        public void DrawBuffer(byte[] buffer)
        {
            SendDrawCommand();

            BitConverter.SwapEndianness(buffer, 2);

            spi.Write(buffer);
            BitConverter.SwapEndianness(buffer, 2);
        }

        public void DrawBuffer(byte[] buffer, int x, int y, int width, int height)
        {
            SetDrawWindow(x, y, width, height);

            DrawBuffer(buffer, x, y, width, height, MaxWidth, 1, 1);
        }

        private void DrawBuffer(byte[] buffer, int x, int y, int width, int height, int originalWidth, int columnMultiplier, int rowMultiplier)
        {

            SendDrawCommand();

            BitConverter.SwapEndianness(buffer, 2);

            spi.Write(buffer, x, y, width, height, originalWidth, columnMultiplier, rowMultiplier);

            BitConverter.SwapEndianness(buffer, 2);
        }


        #region Seesaw functions
        private void WritePinMode(int pins, byte mode)
        {
            var data = new byte[] { (byte)(pins >> 24), (byte)(pins >> 16), (byte)(pins >> 8), (byte)pins };
            var cmd = new byte[6];
            Array.Copy(data, 0, cmd, 2, 4);
            switch (mode)
            {
                case 1: //OUTPUT
                    cmd[0] = SEESAW_GPIO_BASE;
                    cmd[1] = SEESAW_GPIO_DIRSET_BULK;
                    _i2cDevice.Write(cmd);
                    break;
                case 2: //INPUT
                    cmd[0] = SEESAW_GPIO_BASE;
                    cmd[1] = SEESAW_GPIO_DIRCLR_BULK;
                    _i2cDevice.Write(cmd);
                    break;
                case 3: //INPUT PULLUP
                    cmd[0] = SEESAW_GPIO_BASE;
                    cmd[1] = SEESAW_GPIO_DIRCLR_BULK;
                    _i2cDevice.Write(cmd);

                    cmd[1] = SEESAW_GPIO_PULLENSET;
                    _i2cDevice.Write(cmd);

                    cmd[1] = SEESAW_GPIO_BULK_SET;
                    _i2cDevice.Write(cmd);
                    break;
                case 4: //INPUT PULLDOWN
                    cmd[0] = SEESAW_GPIO_BASE;
                    cmd[1] = SEESAW_GPIO_DIRCLR_BULK;
                    _i2cDevice.Write(cmd);

                    cmd[1] = SEESAW_GPIO_PULLENSET;
                    _i2cDevice.Write(cmd);

                    cmd[1] = SEESAW_GPIO_BULK_CLR;
                    _i2cDevice.Write(cmd);
                    break;
                default:
                    break;
            }

        }
        public void SetBacklightIntensityPercentage(double val)
        {
            if (val > 100)
                val = 100;

            if (val < 0)
                val = 0;

            var intensity = (ulong)(0xFFFF - (val / 100 * 0xFFFF));

            _i2cDevice.Write(new byte[] { SEESAW_TIMER_BASE, SEESAW_TIMER_PWM, 0x0, (byte)(intensity >> 8), (byte)intensity });
        }

        void DigitalWrite(int pins, bool value)
        {
            var data = new byte[] { (byte)(pins >> 24), (byte)(pins >> 16), (byte)(pins >> 8), (byte)pins };
            var cmd = new byte[6];
            Array.Copy(data, 0, cmd, 2, 4);
            cmd[0] = SEESAW_GPIO_BASE;
            if (value)
                cmd[1] = SEESAW_GPIO_BULK_SET;
            else
                cmd[1] = SEESAW_GPIO_BULK_CLR;

            _i2cDevice.Write(cmd);
        }
        #endregion
    }
}

Sample App:

namespace Sample
{
    class Program
    {

        static void Main()
        {
            bool isBusy = false;
            int wait = 0;
            var i2cController = I2cController.FromName(GHIElectronics.TinyCLR.Pins.SC20100.I2cBus.I2c1);
            var spi = SpiController.FromName(SC20100.SpiBus.Spi4);
            var gpio = GpioController.GetDefault();

            var min = new AdafruitMiniTFTWithJoystickFeatherwing(i2cController, spi, gpio.OpenPin(SC20100.GpioPin.PD3), gpio.OpenPin(SC20100.GpioPin.PD4));

            // Create flush event
            Graphics.OnFlushEvent += (s, data, x, y, width, height, originalWidth) =>
            {
                min.DrawBuffer(data);
            };

            min.Enable();
            var screen = Graphics.FromImage(new Bitmap(min.Width, min.Height));
            screen.Clear();
            screen.Flush();

            var font16 = Properties.Resources.GetFont(Properties.Resources.FontResources.Arial16);
            var font23 = Properties.Resources.GetFont(Properties.Resources.FontResources.Arial23);

            screen.FillRectangle(new SolidBrush(Color.FromArgb(128, 0, 0, 255)), 0, 0, 160, 80);

            screen.FillRectangle(new SolidBrush(Color.FromArgb(128, 255, 0, 0)), 0, 0, 80, 80);

            min.OnButtonStateChanged += (s, val) =>
            {
                isBusy = true;
                screen.Clear();


                screen.DrawString($"Button {s}", font23, new SolidBrush(Color.White), 0, 0);

                screen.DrawString($"{val}", font16, new SolidBrush(Color.White), 0, 40);
                screen.Flush();
                wait = 5;
                isBusy = false;
            };
            screen.DrawString("Welcome", font16, new SolidBrush(Color.White), 0, 40);
            screen.Flush();

            new Thread(() =>
            {
                while (true)
                {

                    if (!isBusy)
                    {

                        if (wait > 0) { wait--; }
                        else
                        {
                            screen.Clear();
                            var now = DateTime.Now;
                            screen.DrawString(now.ToString("MM/dd/yyyy"), font23, new SolidBrush(Color.White), 0, 0);
                            screen.DrawString(now.ToString("hh:mm:ss"), font16, new SolidBrush(Color.White), 0, 30);
                            screen.Flush();
                        }
                    }
                    Thread.Sleep(1000);


                }
            }).Start();

            Thread.Sleep(-1);
        }
    }
}

3 Likes