### Progress indicator with addressable pixel ring

Simple code for a good looking and versatile progress indicator with 24-LED NeoPixel ring and 7-Segment display

Individually addressable pixel rings are WS2812 based RGB LED strips also known as NeoPixel strips. When interfaced with a microcontroller this strip can display any different RGB color on every LED it contains. They are versatile and can be used in a lot of projects. I bought a 24-LED LED ring which I originally intended to use to build a clock but until then I noticed it could make a nice progress indicator.

In the middle of the LED ring, I added a simple 4-digit 7-segment LED display with TM1637 controller. I opted for red display, yet you may use whatever color you like. Both display and LED ring, together with a buzzer are glued on a disc shaped piece of plastic. I could have 3D printed a better stand, yet the plastic disc does the job.

The equivalent circuit of this is quite simple (so is the code). WS2812 and TM1637 are interfaced to a 5 V Arduino Nano (or compatible board). Keep in mind that the LED ring requires a higher current. I don’t use the LEDs at their full intensity because they are so bright you can hardly look at them.

NeoPixel ring indicator schematic

NeoPixel ring control pin is connected to Arduino pin D6. TM1637 display data wires are connected to pins D2 and D3. I have not yet used the buzzer, but since I have a passive buzzer, it must be wired to a PWM capable pin (i.e., D9) through a 100 ohm resistor. Then sound is then produced using tone(); function of Arduino.

#include <Arduino.h>
#include <FastLED.h>
#include <TM1637Display.h>

#define DIS_CLK 2
#define DIS_DIO 3

#define RING_PIN 6
#define NUM_LEDS 24

uint8_t counter = 0;

uint8_t dispData[4] = {0x00, 0x00, 0x00, 0x00};
TM1637Display disp(DIS_CLK, DIS_DIO);
CRGB ring[NUM_LEDS];
uint8_t led_int[5] = {16, 32, 48, 64, 80};

void progress_reset()
{
counter = 0;

// 7-segment display
dispData[1] = disp.encodeDigit(0);
dispData[2] = disp.encodeDigit(0);
disp.setSegments(dispData);

// LED ring
FastLED.clear();
ring[14].setHSV(0, 255, led_int[4]);
FastLED.show();
}

void display_progress(uint8_t progress_val)
{
if (progress_val > 100)
progress_val = 100; // range check

uint8_t d_progress_val = progress_val; // local copy
dispData[2] = disp.encodeDigit(d_progress_val % 10);
d_progress_val /= 10;
if ((d_progress_val % 10 == 0) && (d_progress_val != 10))
dispData[1] = 0x00;
else
dispData[1] = disp.encodeDigit(d_progress_val % 10);
d_progress_val /= 10;
if (d_progress_val == 0)
dispData[0] = 0x00;
else
dispData[0] = disp.encodeDigit(1);

disp.setSegments(dispData);

// set LED ring
FastLED.clear();
ring[14].setHSV(0, 255, led_int[4]);

for (uint8_t i = 0; i < progress_val; i++)
{
uint8_t curr_led = (15 + i / 5) % 24;
uint8_t curr_led_int = led_int[i % 5];
uint8_t curr_hue = (i / 5) * 5;
ring[curr_led].setHSV(curr_hue, 255, curr_led_int);

/*Serial.print("LED: ");
Serial.print(curr_led);
Serial.print(". Hue=");
Serial.print(curr_hue);
Serial.print(". Int=");
Serial.println(curr_led_int);*/
}
FastLED.show();
}

void setup()
{
// Serial.begin(115200);
delay(2000);

// turn on 7-segment display
disp.setBrightness(7, true);

// init RGB LED ring
FastLED.setBrightness(255);

display_progress(0);
}

void loop()
{
for (uint8_t i = 0; i <= 100; i++)
{
display_progress(i);
delay(150);
}

delay(2000);

for (int8_t i = 100; i >= 0; i--)
{
display_progress(i);
delay(150);
}

delay(2000);
}


There are various color schemes you can use. I opted to shift the hue to get a red to yellow to green transition. There are two libraries for addressable LED strips (Adafruit’s NeoPixel and FastLED). Both support setting HSV colors. However, I like FastLED because hue value is mapped between 0 and 255.

Only 20 LEDs are used. Another one in the bottom is turned on to red by default corresponding to 0 displayed value. The next LEDs start turning on with 5 intensity levels at different hue. With low intensity and when close to a primary color, some LEDs will only display that primary color, because the other’s color intensity is so low that it does not appear to emit light (or is rounded during computation to 0). This is the reason I chose predefined arbitrary LED intensities (see led_int[5] array) instead of computed intensity with global brightness adjustment.

The best color representation is achieved at full intensity, but this makes the LED ring hard to look at since it is too bright.

This is a generic progress indicator with 0-100 display. But it can be changed to suit other purposes like battery voltage display, temperature/humidity display, speedometer, audio volume display etc.

This code snippet has been written in VS Code with PlatformIO. Download it.