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); } } }
Wencey Wang
没必要把数值限制为0-9999吧~不是可以显示负号的么~也可以增加一个16进制模式~