About: PLC, Arduino - Do it yourself project

Today, I'd like to share how to build a Virtual Nixie Tube Clock on RGB led matrix 64x64. The virtual nixie digits are simulated from nixie tube's images.

Before getting started, let's see my videos below.

Update May 15, 2020 - Virtual Nixie Clock with some animations.


Main parts are listed as below:


I reused hardware from the previous INTERACTIVE INTERNET CLOCK project and we can see the details as topic below:

For wiring diagram between ESP32 (NODEMCU-32S) and RGB led matrix 64x64, you can refer to PxMatrix Library by Dominic Buchstaller (2dom).

  • Control board for led matrix 64x64.

  • RGB led matrix picture after finishing assembly work.


There are many Image Converter Tools on the internet for supporting programming images in led matrix, such as:

  • ImageConverter (UTFT): which be used to convert images into hexadecimal code for programming.

  • ezGIF: The online GIF maker and image editor, which is helpful for processing the *.GIF images.

I tried to look for nixie tube images that clearly and beautifully displayed the numbers inside nixie tube. And luckily I found them from a Japanese guy. He also did a virtual nixie tube clock project with M5Stick C and it looks very cool:

I only used his nixie tube images, resized and converted to HEX code by ImageConverter (UTFT) to make a virtual nixie clock on led matrix 64x64 by my way.



Example: HEX code for NIXIE NUMBER "0" - SIZE 20x31 is as follow:

// Generated by : ImageConverter 565 Online
// Generated from : Nixie_20x31_0.png // Time generated : Mon, 11 May 20 17:48:20 +0200 (Server timezone: CET) // Image Size : 20x31 pixels // Memory usage : 1240 bytes

static const uint16_t Nixie_20x31_0[620] PROGMEM={ 0x0000, 0x0020, 0x2944, 0x3986, 0x5A89, 0x7287, 0x82A5, 0x7224, 0x51A3, 0x7A04, 0x59C4, 0x59C4, 0x5A04, 0x59E4, 0x3922, 0x3102, // 0x0010 (16) pixels 0x18A1, 0x1881, 0x20E3, 0x2924, 0x0020, 0x52AB, 0x3165, 0x30A0, 0x61C2, 0x7202, 0x59A2, 0x4962, 0x4142, 0x18A1, 0x28C2, 0x51A3, // 0x0020 (32) pixels 0x7224, 0x7244, 0x7245, 0x8AC5, 0x7A45, 0x18A2, 0x20C2, 0x2903, 0x2965, 0x5206, 0x3901, 0xBCEA, 0xABA6, 0x7244, 0x8AC5, 0x59A3, // 0x0030 (48) pixels 0x3902, 0x28C2, 0x20C2, 0x28E2, 0x59C4, 0x92C6, 0x92E6, 0x9B27, 0x82C6, 0xCC49, 0x4163, 0x3944, 0x4A49, 0x0000, 0x3103, 0xE5AA, // 0x0040 (64) pixels 0x30C1, 0x59C4, 0x3103, 0x18A2, 0x20C2, 0x8262, 0xB302, 0x59C2, 0x20C2, 0x3943, 0x6204, 0x7245, 0x5983, 0xC447, 0x1082, 0x0841, // 0x0050 (80) pixels 0x4A28, 0x0041, 0x3942, 0x40E0, 0x1060, 0x1860, 0x1040, 0x0840, 0x0000, 0x2060, 0x5120, 0x2040, 0x0000, 0x1060, 0x28A1, 0x30E2, // 0x0060 (96) pixels 0x3922, 0x7A04, 0x0062, 0x1081, 0x4A48, 0x0841, 0x3942, 0x69A2, 0x20A1, 0x28A1, 0x18A2, 0x1001, 0xB3A1, 0xA2C0, 0xA201, 0xBB40, // 0x0070 (112) pixels 0x9B01, 0x1001, 0x30E2, 0x4962, 0x59C4, 0xA284, 0x0061, 0x1081, 0x4228, 0x0041, 0x3122, 0x71C2, 0x1040, 0x1860, 0x3062, 0xFD20, // 0x0080 (128) pixels 0x89C0, 0x2001, 0x4020, 0x2000, 0x9A00, 0xF4E0, 0x3883, 0x30E1, 0x51A3, 0x9223, 0x0062, 0x1061, 0x4228, 0x0041, 0x3142, 0x81E2, // 0x0090 (144) pixels 0x1860, 0x1022, 0xFD00, 0x2841, 0x0000, 0x2860, 0x2880, 0x2860, 0x0800, 0x3861, 0xFD80, 0x3084, 0x4142, 0x9A03, 0x0062, 0x1041, // 0x00A0 (160) pixels 0x4208, 0x0841, 0x2901, 0x79C2, 0x1881, 0xBAE2, 0x8181, 0x0821, 0x1860, 0x2060, 0x28A1, 0x2040, 0x1860, 0x0000, 0x9A42, 0xB402, // 0x00B0 (176) pixels 0x30C2, 0xA203, 0x0021, 0x1041, 0x4208, 0x0841, 0x2101, 0x8202, 0x38A2, 0xFCC1, 0x0001, 0x1040, 0x1861, 0x1840, 0x30C1, 0x2060, // 0x00C0 (192) pixels 0x1040, 0x2081, 0x1802, 0xF462, 0x30E3, 0xAA23, 0x0062, 0x1061, 0x4208, 0x0841, 0x20E1, 0x7182, 0xC342, 0x8221, 0x1841, 0x1840, // 0x00D0 (208) pixels 0x1040, 0x1840, 0x20A1, 0x1840, 0x1860, 0x1881, 0x2081, 0x92A3, 0x8241, 0xB203, 0x0041, 0x1041, 0x3A08, 0x0841, 0x18E1, 0x78E2, // 0x00E0 (224) pixels 0xE501, 0x5122, 0x1020, 0x1861, 0x0820, 0x1040, 0x2081, 0x1840, 0x1861, 0x1020, 0x28A1, 0x8A84, 0xE420, 0x9122, 0x0041, 0x1041, // 0x00F0 (240) pixels 0x3A07, 0x0041, 0x18C1, 0xA1E1, 0xD461, 0x2842, 0x1881, 0x1040, 0x0820, 0x1040, 0x2081, 0x2081, 0x1020, 0x0820, 0x3902, 0x40A3, // 0x0100 (256) pixels 0xD401, 0x9962, 0x0041, 0x1061, 0x4208, 0x0041, 0x18A1, 0xCA61, 0xB2C2, 0x2041, 0x28C2, 0x1020, 0x1040, 0x1861, 0x28C2, 0x1860, // 0x0110 (272) pixels 0x1020, 0x1040, 0x4143, 0x3081, 0xA222, 0xAA03, 0x0001, 0x1081, 0x39E7, 0x0041, 0x10C1, 0xF2C0, 0x8281, 0x2881, 0x28A1, 0x20A2, // 0x0120 (288) pixels 0x1881, 0x1881, 0x1881, 0x1881, 0x1881, 0x20A1, 0x30C1, 0x2861, 0xDBE2, 0xC201, 0x0000, 0x1061, 0x39E7, 0x0041, 0x0881, 0xFB00, // 0x0130 (304) pixels 0x6141, 0x4122, 0x1861, 0x1061, 0x1060, 0x1861, 0x1881, 0x1860, 0x1040, 0x20A1, 0x30E2, 0x40E2, 0x9AE1, 0xC200, 0x0000, 0x1061, // 0x0140 (320) pixels 0x39C7, 0x0041, 0x08A1, 0xFAC1, 0x79E2, 0x30E1, 0x1861, 0x1040, 0x0820, 0x1040, 0x1861, 0x1840, 0x1040, 0x20A1, 0x38E2, 0x28E3, // 0x0150 (336) pixels 0xCBA1, 0xC1E0, 0x0000, 0x1061, 0x39C7, 0x0021, 0x1081, 0xF2C1, 0x79E1, 0x30C1, 0x1860, 0x1861, 0x1060, 0x1861, 0x1861, 0x1860, // 0x0160 (352) pixels 0x1860, 0x1881, 0x2081, 0x3861, 0xDBA0, 0xB9E0, 0x0000, 0x1061, 0x31A7, 0x0020, 0x18A1, 0xAA02, 0x6161, 0x28A1, 0x1060, 0x1861, // 0x0170 (368) pixels 0x1061, 0x1060, 0x1861, 0x1860, 0x1881, 0x1040, 0x2081, 0x30C1, 0x7101, 0x9140, 0x0000, 0x1061, 0x31A6, 0x0020, 0x20A1, 0x79E2, // 0x0180 (384) pixels 0xB2C1, 0x2041, 0x2081, 0x1861, 0x1061, 0x1860, 0x1861, 0x1860, 0x1881, 0x1861, 0x30C1, 0x3861, 0xB280, 0x5061, 0x0020, 0x1061, // 0x0190 (400) pixels 0x3186, 0x0020, 0x28C1, 0x5102, 0xD360, 0x2021, 0x1860, 0x1040, 0x1040, 0x1840, 0x1860, 0x1860, 0x1880, 0x1040, 0x28A1, 0x5961, // 0x01A0 (416) pixels 0xCB00, 0x3860, 0x0020, 0x1061, 0x3186, 0x0020, 0x20C1, 0x30A1, 0xD420, 0x5101, 0x1040, 0x1040, 0x1040, 0x1860, 0x1860, 0x2081, // 0x01B0 (432) pixels 0x1840, 0x1840, 0x2881, 0x9A40, 0x4900, 0x58C1, 0x0000, 0x1061, 0x2965, 0x0000, 0x20A1, 0x5121, 0x50E0, 0xC2A0, 0x0000, 0x1860, // 0x01C0 (448) pixels 0x1040, 0x1860, 0x1860, 0x2081, 0x1040, 0x2080, 0x0001, 0xF3C0, 0x0821, 0x58E0, 0x0000, 0x1061, 0x2945, 0x0000, 0x20A0, 0x5101, // 0x01D0 (464) pixels 0x1841, 0xFC80, 0x0000, 0x1840, 0x1840, 0x1840, 0x2060, 0x2061, 0x1860, 0x1021, 0x8A20, 0x8220, 0x0001, 0x48E0, 0x0000, 0x1061, // 0x01E0 (480) pixels 0x2124, 0x0000, 0x2080, 0x40E0, 0x1860, 0x48C1, 0xF4A0, 0x0001, 0x1840, 0x2060, 0x2060, 0x2060, 0x1021, 0x5121, 0xFC20, 0x0001, // 0x01F0 (496) pixels 0x0820, 0x40E0, 0x0020, 0x1041, 0x2104, 0x0000, 0x1860, 0x38C0, 0x1840, 0x1040, 0x8180, 0xC2C0, 0x1040, 0x2000, 0x2040, 0x1800, // 0x0200 (512) pixels 0x7140, 0xCAC0, 0x1001, 0x1040, 0x0000, 0x38C0, 0x0020, 0x1041, 0x18E3, 0x0000, 0x1860, 0x30C0, 0x1040, 0x2060, 0x0000, 0x51A0, // 0x0210 (528) pixels 0xB2A0, 0x7160, 0x38A0, 0xB1E0, 0x79E0, 0x1021, 0x0820, 0x1040, 0x0820, 0x30C0, 0x0020, 0x1061, 0x10C2, 0x0000, 0x1860, 0x2880, // 0x0220 (544) pixels 0x0820, 0x1020, 0x0000, 0x0000, 0x0000, 0x5100, 0x7A00, 0x30C0, 0x0000, 0x0800, 0x0000, 0x0000, 0x0000, 0x30A0, 0x0000, 0x0841, // 0x0230 (560) pixels 0x10A2, 0x0000, 0x0000, 0x18A2, 0x2902, 0x3943, 0x20C2, 0x20E3, 0x18A2, 0x3922, 0x3901, 0x3962, 0x3943, 0x4184, 0x3984, 0x3164, // 0x0240 (576) pixels 0x3123, 0x2924, 0x0000, 0x1041, 0x0881, 0x0000, 0x0800, 0x9344, 0x4142, 0x1060, 0x0840, 0x1861, 0x1040, 0x0020, 0x1040, 0x0820, // 0x0250 (592) pixels 0x1061, 0x1061, 0x1881, 0x28C1, 0x18A1, 0x8A64, 0x18A1, 0x18A2, 0x0861, 0x0000, 0x0000, 0x51E2, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0260 (608) pixels };

I converted all nixie number images into a HEX codes for next programming step.


The project code for VIRTUAL NIXIE CLOCK ON LED MATRIX 64X64 is available at my GitHub.

My program is explained as follows:

  • Nixie Number Image: There are 2 sets of Nixie number libraries with different sizes, named SMALL and BIG size number:

- Nixie_20x31.h

- Nixie_32x61.h

  • Internet Clock:

- Time is updated via internet by NODEMCU-32S and hour/ minute/ second digits are displayed by corresponding nixie number pictures. The tens and units digit of time will be lookup in to picture arrays as below:

/* Small Nixie Number*/
const uint16_t *Numbers_S[10] =

/* Big Nixie Number*/
const uint16_t *Numbers_B[10] =

- When the seconds in range 0 to 30, led matrix 64x64 display all digits of hour, minute and second in SMALL nixie numbers. The seconds from 31 to 59 will be displayed in BIG nixie numbers. At this time, led matrix 64x64 only show the tens and units digit of second.

if (seconds>=0 && seconds<=30) // Show all digits when seconds is from 0 ~ 30 in Small Nixie Number.<br>    {   
      if (seconds==0)
        NixieNumbers.clearNumber(0, 0, NUMBER_WIDTH_B, NUMBER_HEIGHT_B);    // Clear Big Nixie Number
        NixieNumbers.clearNumber(32, 0, NUMBER_WIDTH_B, NUMBER_HEIGHT_B);   // Clear Big Nixie Number
    else   // Show only second from 31 ~ 59 in Big Nixie Number.
      if (seconds==31)
        NixieNumbers.clearNumber(0, 0, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);    // Clear Small Nixie Number
        NixieNumbers.clearNumber(0, 33, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);   // Clear Small Nixie Number
        NixieNumbers.clearNumber(22, 0, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);   // Clear Small Nixie Number
        NixieNumbers.clearNumber(22, 33, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);  // Clear Small Nixie Number
        NixieNumbers.clearNumber(44, 0, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);   // Clear Small Nixie Number
        NixieNumbers.clearNumber(44, 33, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);  // Clear Small Nixie Number

- At the units and tens digit of hour, minute and second, before showing the newly updated time, we have to erase the time that was shown previously. For example:

void Nixie_S0()
  if ((unsigned long) (micros() - samplingtimes0) > 99)
      if (s0 != prves0)  
        NixieNumbers.clearNumber(44, 33, NUMBER_WIDTH_S, NUMBER_HEIGHT_S);
        NixieNumbers.drawNumber(44, 33, Numbers_S[s0], NUMBER_WIDTH_S, NUMBER_HEIGHT_S);       
        prves0 = s0;
        NixieNumbers.drawNumber(44, 33, Numbers_S[prves0], NUMBER_WIDTH_S, NUMBER_HEIGHT_S);
      samplingtimes0 = micros(); 

Here below is my result picture.

Step 5: DONE

It looks more beautiful when we see it in real life and stand far from the led screen. The flickers you see on videos is due to CAMERA, human eyes can't see these flickers.

If we have a led screen with big size and high resolution, this nixie clock will looks even better.

Arduino Contest 2020

Participated in the
Arduino Contest 2020