Proportionally Lighting 24 LED's Representing 0 to 99

I couldn't come up with a more descriptive title of what I'm trying to do. I have a 24 LED neopixel ring. I'm trying to light the LED's such that they represent values from 0 to 99. I'm just writing some basic code to get the idea across.

At the most basic level this works...

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
    while(!Serial);
  Serial.println("Begin");
  
  for (int i = 0; i<100; i++){
    int pixel = map(i, 0, 99, 0, 23);
    Serial.print("input=");
    Serial.print(i);
    Serial.print(" pixel=");
    Serial.println(pixel);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

The code above will tell me which pixel to light based on values from 0 to 99. With this I can only light a single pixel to represent the value of the input. What I'm trying to accomplish is to proportionally light two of the LED's to represent the values in between. If this ring had 25 LED's instead of 24 this is a fairly easy solution. If it had 25 LED's then the map value would always be a multiple of 4. For instance, if the input was 0 pixel 0 is 100% lit and pixel 1 is 0% lit. If the input was 1 pixel 0 would be 75% lit and pixel one would be 25% lit. And so on around the ring.

With 24 pixels however it gets more complicated because as the input goes from 0 to 99 there will be 4 occasions where the mapped value will be 5 instead of 4. In those instances I need the brightness value in 20% increments. I hope I'm explaining this well enough that it makes sense. Below is code I wrote that would work if this thing had 25 LED's

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
    while(!Serial);
  Serial.println("Begin");
  
  for (int i = 0; i<100; i++){
    int pixel = map(i, 0, 99, 0, 23);
    Serial.print("input=");
    Serial.print(i);
    Serial.print(" pixel=");
    Serial.println(pixel);
  }
}

void loop() {
  // put your main code here, to run repeatedly:

}

If I print the serial output the brightness values are represented as 0 to 3 but in 4 of those instances I need it to be from 0 to 4.

test
input=0 pixel=0 pixelBrightness=3 nextPixel=1 nextPixelBrightness=0
input=1 pixel=0 pixelBrightness=2 nextPixel=1 nextPixelBrightness=1
input=2 pixel=0 pixelBrightness=1 nextPixel=1 nextPixelBrightness=2
input=3 pixel=0 pixelBrightness=0 nextPixel=1 nextPixelBrightness=3
input=4 pixel=0 pixelBrightness=3 nextPixel=1 nextPixelBrightness=0
input=5 pixel=1 pixelBrightness=2 nextPixel=2 nextPixelBrightness=1
input=6 pixel=1 pixelBrightness=1 nextPixel=2 nextPixelBrightness=2
input=7 pixel=1 pixelBrightness=0 nextPixel=2 nextPixelBrightness=3
input=8 pixel=1 pixelBrightness=3 nextPixel=2 nextPixelBrightness=0
input=9 pixel=2 pixelBrightness=2 nextPixel=3 nextPixelBrightness=1
input=10 pixel=2 pixelBrightness=1 nextPixel=3 nextPixelBrightness=2
input=11 pixel=2 pixelBrightness=0 nextPixel=3 nextPixelBrightness=3
input=12 pixel=2 pixelBrightness=3 nextPixel=3 nextPixelBrightness=0
input=13 pixel=3 pixelBrightness=2 nextPixel=4 nextPixelBrightness=1
input=14 pixel=3 pixelBrightness=1 nextPixel=4 nextPixelBrightness=2
input=15 pixel=3 pixelBrightness=0 nextPixel=4 nextPixelBrightness=3
input=16 pixel=3 pixelBrightness=3 nextPixel=4 nextPixelBrightness=0
input=17 pixel=3 pixelBrightness=2 nextPixel=4 nextPixelBrightness=1
input=18 pixel=4 pixelBrightness=1 nextPixel=5 nextPixelBrightness=2
input=19 pixel=4 pixelBrightness=0 nextPixel=5 nextPixelBrightness=3
input=20 pixel=4 pixelBrightness=3 nextPixel=5 nextPixelBrightness=0
input=21 pixel=4 pixelBrightness=2 nextPixel=5 nextPixelBrightness=1
input=22 pixel=5 pixelBrightness=1 nextPixel=6 nextPixelBrightness=2
input=23 pixel=5 pixelBrightness=0 nextPixel=6 nextPixelBrightness=3
input=24 pixel=5 pixelBrightness=3 nextPixel=6 nextPixelBrightness=0
input=25 pixel=5 pixelBrightness=2 nextPixel=6 nextPixelBrightness=1
input=26 pixel=6 pixelBrightness=1 nextPixel=7 nextPixelBrightness=2
input=27 pixel=6 pixelBrightness=0 nextPixel=7 nextPixelBrightness=3
input=28 pixel=6 pixelBrightness=3 nextPixel=7 nextPixelBrightness=0
input=29 pixel=6 pixelBrightness=2 nextPixel=7 nextPixelBrightness=1
input=30 pixel=6 pixelBrightness=1 nextPixel=7 nextPixelBrightness=2
input=31 pixel=7 pixelBrightness=0 nextPixel=8 nextPixelBrightness=3
input=32 pixel=7 pixelBrightness=3 nextPixel=8 nextPixelBrightness=0
input=33 pixel=7 pixelBrightness=2 nextPixel=8 nextPixelBrightness=1
input=34 pixel=7 pixelBrightness=1 nextPixel=8 nextPixelBrightness=2
input=35 pixel=8 pixelBrightness=0 nextPixel=9 nextPixelBrightness=3
input=36 pixel=8 pixelBrightness=3 nextPixel=9 nextPixelBrightness=0
input=37 pixel=8 pixelBrightness=2 nextPixel=9 nextPixelBrightness=1
input=38 pixel=8 pixelBrightness=1 nextPixel=9 nextPixelBrightness=2

Hopefully this shows the problem.

I could simply create some kind of mapping table and hard code the values but it seems like this should be solvable with fewer lines of code than doing that.

What I'm ultimately trying to get to is code where I can pass a value from 0 to 99 and have it tell me what 2 pixels to light and at what percentage brightness or more accurately what value from 0 to 255 to light each pixel.

It seems odd to me that an input of 0 lights up any LEDs, but this code does what you want.

Note that for a value of 99, the last LED is not fully ON, you need to go from 0-100.

const int LEDcount = 24;
const float scale = 100.0 / LEDcount;


void setup() {
  Serial.begin(9600);


  for (int i = 0; i < 100; i++ ) {
    float val = i / scale;

    int led1 = int(val);
    int led2 = led1 + 1;

    int brightness1 = 255 - int((val - led1) * 255);
    int brightness2 = 255 - brightness1;

    Serial.print( "input=" ); Serial.print(i);
    Serial.print( " led1="); Serial.print(led1);
    Serial.print( " at "); Serial.print(brightness1);
    Serial.print( " led2="); Serial.print(led2);
    Serial.print( " at "); Serial.print(brightness2);
    Serial.println();

  }


}

void loop() {

}

output

input=0 led1=0 at 255 led2=1 at 0
input=1 led1=0 at 194 led2=1 at 61
input=2 led1=0 at 133 led2=1 at 122
input=3 led1=0 at 72 led2=1 at 183
input=4 led1=0 at 11 led2=1 at 244
input=5 led1=1 at 204 led2=2 at 51
input=6 led1=1 at 143 led2=2 at 112
input=7 led1=1 at 82 led2=2 at 173
input=8 led1=1 at 21 led2=2 at 234
input=9 led1=2 at 215 led2=3 at 40
input=10 led1=2 at 153 led2=3 at 102
input=11 led1=2 at 92 led2=3 at 163
input=12 led1=2 at 31 led2=3 at 224
input=13 led1=3 at 225 led2=4 at 30
input=14 led1=3 at 164 led2=4 at 91
input=15 led1=3 at 102 led2=4 at 153
input=16 led1=3 at 41 led2=4 at 214
input=17 led1=4 at 235 led2=5 at 20
input=18 led1=4 at 174 led2=5 at 81
input=19 led1=4 at 113 led2=5 at 142
input=20 led1=4 at 51 led2=5 at 204
input=21 led1=5 at 245 led2=6 at 10
input=22 led1=5 at 184 led2=6 at 71
input=23 led1=5 at 123 led2=6 at 132
input=24 led1=5 at 62 led2=6 at 193
input=25 led1=6 at 255 led2=7 at 0
input=26 led1=6 at 194 led2=7 at 61
input=27 led1=6 at 133 led2=7 at 122
input=28 led1=6 at 72 led2=7 at 183
input=29 led1=6 at 11 led2=7 at 244
input=30 led1=7 at 204 led2=8 at 51
input=31 led1=7 at 143 led2=8 at 112
input=32 led1=7 at 82 led2=8 at 173
input=33 led1=7 at 21 led2=8 at 234
input=34 led1=8 at 215 led2=9 at 40
input=35 led1=8 at 153 led2=9 at 102
input=36 led1=8 at 92 led2=9 at 163
input=37 led1=8 at 31 led2=9 at 224
input=38 led1=9 at 225 led2=10 at 30
input=39 led1=9 at 164 led2=10 at 91
input=40 led1=9 at 102 led2=10 at 153
input=41 led1=9 at 41 led2=10 at 214
input=42 led1=10 at 235 led2=11 at 20
input=43 led1=10 at 174 led2=11 at 81
input=44 led1=10 at 113 led2=11 at 142
input=45 led1=10 at 51 led2=11 at 204
input=46 led1=11 at 245 led2=12 at 10
input=47 led1=11 at 184 led2=12 at 71
input=48 led1=11 at 123 led2=12 at 132
input=49 led1=11 at 62 led2=12 at 193
input=50 led1=12 at 255 led2=13 at 0
input=51 led1=12 at 194 led2=13 at 61
input=52 led1=12 at 133 led2=13 at 122
input=53 led1=12 at 72 led2=13 at 183
input=54 led1=12 at 11 led2=13 at 244
input=55 led1=13 at 204 led2=14 at 51
input=56 led1=13 at 143 led2=14 at 112
input=57 led1=13 at 82 led2=14 at 173
input=58 led1=13 at 21 led2=14 at 234
input=59 led1=14 at 215 led2=15 at 40
input=60 led1=14 at 153 led2=15 at 102
input=61 led1=14 at 92 led2=15 at 163
input=62 led1=14 at 31 led2=15 at 224
input=63 led1=15 at 225 led2=16 at 30
input=64 led1=15 at 164 led2=16 at 91
input=65 led1=15 at 102 led2=16 at 153
input=66 led1=15 at 41 led2=16 at 214
input=67 led1=16 at 235 led2=17 at 20
input=68 led1=16 at 174 led2=17 at 81
input=69 led1=16 at 113 led2=17 at 142
input=70 led1=16 at 51 led2=17 at 204
input=71 led1=17 at 245 led2=18 at 10
input=72 led1=17 at 184 led2=18 at 71
input=73 led1=17 at 123 led2=18 at 132
input=74 led1=17 at 62 led2=18 at 193
input=75 led1=18 at 255 led2=19 at 0
input=76 led1=18 at 194 led2=19 at 61
input=77 led1=18 at 133 led2=19 at 122
input=78 led1=18 at 72 led2=19 at 183
input=79 led1=18 at 11 led2=19 at 244
input=80 led1=19 at 204 led2=20 at 51
input=81 led1=19 at 143 led2=20 at 112
input=82 led1=19 at 82 led2=20 at 173
input=83 led1=19 at 21 led2=20 at 234
input=84 led1=20 at 215 led2=21 at 40
input=85 led1=20 at 153 led2=21 at 102
input=86 led1=20 at 92 led2=21 at 163
input=87 led1=20 at 31 led2=21 at 224
input=88 led1=21 at 225 led2=22 at 30
input=89 led1=21 at 164 led2=22 at 91
input=90 led1=21 at 102 led2=22 at 153
input=91 led1=21 at 41 led2=22 at 214
input=92 led1=22 at 235 led2=23 at 20
input=93 led1=22 at 174 led2=23 at 81
input=94 led1=22 at 113 led2=23 at 142
input=95 led1=22 at 51 led2=23 at 204
input=96 led1=23 at 245 led2=24 at 10
input=97 led1=23 at 184 led2=24 at 71
input=98 led1=23 at 123 led2=24 at 132
input=99 led1=23 at 62 led2=24 at 193

Wow, thanks. That's exactly what I was looking for and just couldn't wrap my head around it.

The application for this is a tachometer. The ring indicates the RPM from 0 to 99. In the middle of the tach face is a display that shows the RPM in hundreds. That's the reason than an input of 0 lights up the 0 LED.

Only flaw is that with an input of 96 to 99 it's saying led2=24. There are 24 LED's from 0 to 23 so 24 isn't valid. Easy fix which I made already.

Don't you want green-yellow-red..engine blown on it?

-jim lee

Perhaps if it were going in a vehicle. This is for a metal lathe. Will be a quad tachometer. Measures spindle RPM, motor RPM, varidrive RPM, and by measuring the feed rod RPM will calculate feed per revolution.

travisr100:
Perhaps if it were going in a vehicle. This is for a metal lathe. Will be a quad tachometer. Measures spindle RPM, motor RPM, varidrive RPM, and by measuring the feed rod RPM will calculate feed per revolution.

In that case, your input range of 0-99 sounds pretty arbitrary.
How are you measuring the RPM?

Pieter

I'm not sure I understand what you mean. For instance, if the RPM is 3325 then the display in the middle will show 33 and the LED's on the ring will indicated the 25 (where 1/4 of the ring would be lit to indicate 25). Does that make more sense?

Either hall or IR sensors. I've got a mix of both on the bench I'm testing with. I've considered using a quadrature encoder simply because at the lowest RPM range it would take a while to update the displays with one pulse per revolution. But as a practical matter will rarely run it below 100 RPM.

This video should make it more clear. Forgive the bad display. New one on order.

In the green boxes below the main RPM is where the other three sensor readings will go. The "x100" disappears from the display when the RPM falls below 100.

You'll probably measure the RPM as a float. Use that float value, don't throw away the precision you have.

If 3325 is 25% of the scale, 13300 is 100%.

float rpm = ...;
float scaled = rpm * 24 / 13300;
uint8_t leds = scaled; // number of LEDs that are fully on
uint8_t finalBrightness = 255 * (scaled - leds); // brightness of the last LED

I don't get what the ring is showing. When your shaft change speeds, it spins around and around. What information is this spinning communicating to you?

-jim lee

JimLee, the ring is always indicating the RPM's less than a hundred. I thought the video would make it pretty clear.

If the RPM is 2550...
The display says "25" and the ring has the pixel at the 6:00 position lit..

If the RPM is 1325...
The display says "13" and the ring has the pixel at the 3:00 position lit.

If the RPM is 3275...
The display says "32" and the ring has the pixel at the 9:00 position lit.

If the RPM is 2200...
The display says 22 and the ring has the pixel at the 12:00 position lit.

Yes, when you change speeds it does spin around as the RPM is constantly changing as the motor accelerates. If you look closely in the video you can see the lit pixel has a "tail" on it as it spins around. That tail represents the acceleration of the motor at any given moment. Does this make sense now? If not I can make another video and narrate it.

PieterP, I'm not measuring the RPM as a float and I think you misunderstood as well. The ring always represents the two right most digits of the RPM, then tens and ones places. The display always represents the thousands and hundreds places. When I light the pixel on the ring I'm only looking at the two right most digits of the RPM which is why when I originally asked my question I said I was only trying to map from 0 to 99.

Here is the tach I'm replacing...

For some reason I can't edit my above post. But if you look at the attached pic of the tach I'm replacing you can see that it too indicates RPM in hundreds. Says so right on the gauge. You can also see that the division between each hundred RPM is very small. So small that it's hard to tell with much more resolution than 100 what speed you're running at. If I were running at 550 RPM the needle would be between the 5 and 6 mark. That's a very small space and hard to see very well particularly with as jumpy as this old tach is. The ring fixes that problem. The ring represents that space between tick marks on the gauge.

If the 6:00 pixel were lit you'd know you were halfway between two hundreds of RPM values. For instance you'd know you were at 550 RPM, halfway between the 5 and 6 hundred tick marks on the mechanical gauge.

Imagine the ring had 100 LED's on it. If you were going 550 RPM LED 50 would be list. If you were going 3250 RPM LED 50 would still be lit.

This video should eliminate all confusion. Instead of lighting a single pixel I'm lighting a portion of the entire ring to make my point.

So its like picking up the last digit, like a veneer thing. Ok, I think I see what your up to.

Thanks!

-jim lee

jimLee:
So its like picking up the last digit, like a veneer thing. Ok, I think I see what your up to.

Thanks!

-jim lee

Exactly! It's actually picking up the last 2 digits. Very much like a vernier scale in functionality.

Happy to post the code if anyone is interested.

I finally got this temporarily mounted on the machine. Here it is working...

Now that's pretty cool! Would you like a running average thing to smooth out the readings a little?

-jim lee

jimLee:
Now that's pretty cool! Would you like a running average thing to smooth out the readings a little?

-jim lee

I’m not sure what you mean. I already use a running average to smooth out the readings. At least on the main display. The other 3 RPM values at the bottom are not using an average.

I’ve seen several different ways of doing it. A guy on YouTube named InterlinkKnight has a video of a tach he made. Here’s a link...

I took some of my ideas from him. He is dynamically averaging based on RPM/period just like I am. His approach used a lot of code in the ISR however. My goal was to drastically reduce this.

He’s doing averaging by looking at the pulse duration and if it gets short enough (higher RPM) he takes multiple readings of the pulses and takes an average of the RPM readings. In his implementation, if for instance he wants to take 5 readings, he is making 5 trips through the ISR, accumulating the readings, taking the average, then displaying it. He’s doing all this in the ISR. He’s also requiring 5 trips through the ISR before he’s able to calculate that average and display a value.

In my implementation, I’m also looking at the pulse duration on any given trip through the ISR and determining how many values to average for the display. So at say 100 RPM I’m not using any average, just the last value. At 200 RPM I’m using the average of the last 2 values. At 400 RPM, the last 3 values. At 800 RPM, the last 4 values, etc. But I’m storing these values in a circular array/buffer with each trip through the ISR. My arrray always has the last 10 values stored and looks back at the last X number of values to calculate the RPM to calculate an average where X is determined by the pulse duration. Faster the rotation, the larger number of values used to calculate the average. Doing it this way I can calculate and display a value with each trip through the ISR instead of having to wait on X trips to do it. I suppose you can call this dynamic smoothing/averaging.

You’ll also notice that the main RPM display has virtually NO flicker as the numbers change. If you look at the other 2 RPM values at the bottom you’ll see noticeable flicker as the values change. For the values at the bottom, I’m not doing anything to attempt to reduce flicker. I’m simply clearing what was previously there then displaying the new value each time I calculate the RPM. On the main RPM there is no flicker. For the main RPM I’m only ever displaying 2 numbers. Each time I get a new RPM I...

  1. Take the value, for example 23, and split that into two separate digits, 2 and 3.
  2. I look to see if the first digit has changed since the last RPM reading.
  3. If it hasn’t changed, I do nothing. I leave that digit on the display.
  4. If it has changed, I write the old digit first using the background color to clear it then I write the new digit in the foreground color.
  5. I repeat the process for the 2nd digit.

Only writing a digit if it has changed virtually eliminates the flicker.