One of the commonly used LED Tube driver chip is 74HC595, by using the Chip, you can save GPIO ports. I bought an LED Tube with 595 chip, it only needs 3 GPIO ports to display 4 digits. However, there is no existing posts about how to drive the 74HC595 LED Tube from Windows 10. So, I can only try for myself.

1. Physical Connection


VCC to DC3.3 power, GND to Ground, DIO to GPIO27 (you can change it, and change the program also). SCLK to gpio 5 (you can also change), RCLK to GPIO 06.

As shown below:

2. Coding


The shop give me a C++ code on Arduino:

/*******************************************************************************	
* Software Author:	HQ	
* Creation Date:        2015-2-10 
* Software History:	2015-3-20
* Version:              2.0 
* Sales address:       http://qifeidz.taobao.com/
********************************************************************************/

    unsigned char LED_0F[] = 
    {// 0	 1	  2	   3	4	 5	  6	   7	8	 9	  A	   b	C    d	  E    F    -
    	0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90,0x8C,0xBF,0xC6,0xA1,0x86,0xFF,0xbf
    };
    unsigned char LED[4];	//用于LED的4位显示缓存
    int SCLK = 2;
    int RCLK = 1;
    int DIO = 0; //这里定义了那三个脚
    void setup ()
    {
      pinMode(SCLK,OUTPUT);
      pinMode(RCLK,OUTPUT);
      pinMode(DIO,OUTPUT); //让三个脚都是输出状态
    }
    void loop()
    {
        LED[0]=1;
    	LED[1]=2;
    	LED[2]=3;
    	LED[3]=4;
    	while(1)
    	{
    		LED4_Display ();
    	} 
      
    }
    
    void LED4_Display (void)
    {
    	unsigned char *led_table;          // 查表指针
    	unsigned char i;
    	//显示第1位
    	led_table = LED_0F + LED[0];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x01);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第2位
    	led_table = LED_0F + LED[1];
    	i = *led_table;
    	LED_OUT(i);		
    	LED_OUT(0x02);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第3位
    	led_table = LED_0F + LED[2];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x04);	
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    	//显示第4位
    	led_table = LED_0F + LED[3];
    	i = *led_table;
    	LED_OUT(i);			
    	LED_OUT(0x08);		
        digitalWrite(RCLK,LOW);
        digitalWrite(RCLK,HIGH);
    }
    
    void LED_OUT(unsigned char X)
    {
    	unsigned char i;
    	for(i=8;i>=1;i--)
    	{
    		if (X&0x80) 
                {
                  digitalWrite(DIO,HIGH);
                 }  
                else 
                {
                  digitalWrite(DIO,LOW);
                }
    		X<<=1;
                digitalWrite(SCLK,LOW);
                digitalWrite(SCLK,HIGH);
    	}
    }
    

We only need to display Numbers. So we only need 0-9 from the LED_0F array, the C# definition is this:

byte[] LEDDIGITS =
{
    // 0    1    2    3    4	5	 6	  7	   8    9
    0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90
};

GPIO Pins:

public GpioPin PinDIO { get; set; }     // Serial Digital Input
public GpioPin PinRCLK { get; set; }   // Register Clock
public GpioPin PinSCLK { get; set; }   // Serial Clock

Initiate GPIO Controller and Pins. 

public LED74HC595Driver(int dioPin, int rclkPin, int sclkPin)
{
    var gpio = GpioController.GetDefault();

    // setup the pins
    PinDIO = gpio.OpenPin(dioPin);
    PinDIO.SetDriveMode(GpioPinDriveMode.Output);

    PinRCLK = gpio.OpenPin(rclkPin);
    PinRCLK.SetDriveMode(GpioPinDriveMode.Output);

    PinSCLK = gpio.OpenPin(sclkPin);
    PinSCLK.SetDriveMode(GpioPinDriveMode.Output);

    // initialize the pins to low
    PinDIO.Write(GpioPinValue.Low);
    PinRCLK.Write(GpioPinValue.Low);
    PinSCLK.Write(GpioPinValue.Low);
}

C# translation of "void LED_OUT(unsigned char X)"

// Serial-In-Parallel-Out
void WriteSIPO(byte b)
{
    for (int i = 8; i >= 1; i--)
    {
        PinDIO.Write((b & 0x80) > 0 ? GpioPinValue.High : GpioPinValue.Low);
        b <<= 1;
        PulseSCLK();
    }
}

In order to use it more handy, add 2 helper methods

// Pulse Register Clock
void PulseRCLK()
{
    PinRCLK.Write(GpioPinValue.Low);
    PinRCLK.Write(GpioPinValue.High);
}

// Pulse Serial Clock
void PulseSCLK()
{
    PinSCLK.Write(GpioPinValue.Low);
    PinSCLK.Write(GpioPinValue.High);
}

Now we can refact the logic of "void LED4_Display (void)". Create a new method, to display a single number on a particular tube.

public void DisplayChar(int index, byte b)
{
    if (index < 0 || index > 3)
    {
        throw new ArgumentOutOfRangeException(nameof(index), "index just be between 0-3.");
    }

    int i = 0x01;
    i = i << index;

    WriteSIPO(b);
    WriteSIPO((byte)i);
    PulseRCLK();
}

The index stands for different tubes from right to left. 0 is the 1st tube, 2 is the second, 3 is the third, 4 is the last. Because it is 4 digits, it can not excceed 9999, so remember to check the range. I didn't consider the nagetive numbers, so it will be 0-9999.

For example, you want to display "8" on the second tube, like _ _ 8 _

DisplayChar(1, 0x80)

the number "8" is already defined in LEDDIGITS.

In order to be easy to use, write a method, take int type number, then convert to byte to display.

public void DisplayDigits(int number)
{
    if (number < 0 || number > 9999)
    {
        throw new ArgumentOutOfRangeException(nameof(number), "number must be between 0-9999");
    }

    // 老司机写法
    var numStr = number.ToString();
    for (int i = 0; i < numStr.Length; i++)
    {
        var pos = numStr[numStr.Length - i - 1] - '0';
        DisplayChar(i, LEDDIGITS[pos]);
    }

    // 新司机写法
    //// 个位
    //var ge = number % 10;

    //// 十位
    //var shi = number / 10 % 10;

    //// 百位
    //var bai = number / 100 % 10;

    //// 千位
    //var qian = number / 1000 % 10;

    //DisplayChar(0, LEDDIGITS[ge]);
    //DisplayChar(1, LEDDIGITS[shi]);
    //DisplayChar(2, LEDDIGITS[bai]);
    //DisplayChar(3, LEDDIGITS[qian]);
}

Then is the difficult part. How the LED Tube works is, by refreshing the characters on each tube for a very fast rate, human eye can not see the refresh, it will show like there are 4 digits at the same time. So that's why there are a while(1) loop in that C++ code.

Translating to C#, in UWP, you will need a background worker so your app start page don't stuck.

public sealed partial class MainPage : Page
{
    public LED74HC595Driver Led74Hc595Driver { get; set; }

    public MainPage()
    {
        InitializeComponent();

        Led74Hc595Driver = new LED74HC595Driver(26, 6, 5);

        BackgroundWorker worker = new BackgroundWorker { WorkerSupportsCancellation = true };
        worker.DoWork += (sender, args) =>
        {
            while (true)
            {
                Led74Hc595Driver.DisplayDigits(1024);
            }
        };

        Loaded += (sender, args) =>
        {
            worker.RunWorkerAsync();
        };
    }
}

Finally, the complete code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Gpio;

namespace LEDTube74HC595
{
    public class LED74HC595Driver
    {
        byte[] LEDDIGITS =
        {
            // 0    1    2    3    4	5	 6	  7	   8    9
    	    0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90
        };

        private byte[] LEDCHARS =
        {
            // A     b     C     d     E     F     -
            0x8C, 0xBF, 0xC6, 0xA1, 0x86, 0xFF, 0xbf
        };

        public GpioPin PinDIO { get; set; }     // Serial Digital Input
        public GpioPin PinRCLK { get; set; }   // Register Clock
        public GpioPin PinSCLK { get; set; }   // Serial Clock

        public LED74HC595Driver(int dioPin, int rclkPin, int sclkPin)
        {
            var gpio = GpioController.GetDefault();

            // setup the pins
            PinDIO = gpio.OpenPin(dioPin);
            PinDIO.SetDriveMode(GpioPinDriveMode.Output);

            PinRCLK = gpio.OpenPin(rclkPin);
            PinRCLK.SetDriveMode(GpioPinDriveMode.Output);

            PinSCLK = gpio.OpenPin(sclkPin);
            PinSCLK.SetDriveMode(GpioPinDriveMode.Output);

            // initialize the pins to low
            PinDIO.Write(GpioPinValue.Low);
            PinRCLK.Write(GpioPinValue.Low);
            PinSCLK.Write(GpioPinValue.Low);
        }

        public void DisplayChar(int index, byte b)
        {
            if (index < 0 || index > 3)
            {
                throw new ArgumentOutOfRangeException(nameof(index), "index just be between 0-3.");
            }

            int i = 0x01;
            i = i << index;

            WriteSIPO(b);
            WriteSIPO((byte)i);
            PulseRCLK();
        }

        public void DisplayDigits(int number)
        {
            if (number < 0 || number > 9999)
            {
                throw new ArgumentOutOfRangeException(nameof(number), "number must be between 0-9999");
            }

            // 老司机写法
            var numStr = number.ToString();
            for (int i = 0; i < numStr.Length; i++)
            {
                var pos = numStr[numStr.Length - i - 1] - '0';
                DisplayChar(i, LEDDIGITS[pos]);
            }

            // 新司机写法
            //// 个位
            //var ge = number % 10;

            //// 十位
            //var shi = number / 10 % 10;

            //// 百位
            //var bai = number / 100 % 10;

            //// 千位
            //var qian = number / 1000 % 10;

            //DisplayChar(0, LEDDIGITS[ge]);
            //DisplayChar(1, LEDDIGITS[shi]);
            //DisplayChar(2, LEDDIGITS[bai]);
            //DisplayChar(3, LEDDIGITS[qian]);
        }

        // Serial-In-Parallel-Out
        void WriteSIPO(byte b)
        {
            for (int i = 8; i >= 1; i--)
            {
                PinDIO.Write((b & 0x80) > 0 ? GpioPinValue.High : GpioPinValue.Low);
                b <<= 1;
                PulseSCLK();
            }
        }

        // Pulse Register Clock
        void PulseRCLK()
        {
            PinRCLK.Write(GpioPinValue.Low);
            PinRCLK.Write(GpioPinValue.High);
        }

        // Pulse Serial Clock
        void PulseSCLK()
        {
            PinSCLK.Write(GpioPinValue.Low);
            PinSCLK.Write(GpioPinValue.High);
        }
    }
}

3. Running