Zápisník experimentátora
Hierarchy: WS2812
The NeoPixel Ring contains at each point three LEDs that make up a pixel. The resulting color is set using three RGB components. Such coloring is complicated for man, because not everyone can imagine the resulting color, which consists of three components. To facilitate color adjustment, a HSV (hue, saturation, value) model is used to mix these colors with:
In this article, we will try to set up the NeoPixel Ring using the HSV model. For each component of the HSV model we will write a sample program that will display it.
Protective components are necessary in order to not damage NeoPixel Ring. Details of protection are in article on powering these diodes.
The picture shows a ring with 12 diodes. Ring with 24 diodes is not very different from it and it is therefore possible to use a smaller ring after minor changes in the sketch.
In this example, individual color shades appear on the NeoPixel Ring. The color shade is set in the range 0-MAXHUE
. Saturation is set to the maximum value and the brightness has a minimum value to make the best possible conditions for taking pictures. Color distribution to individual pixels is done using the simple formula i * (MAXHUE / CNT)
. Function getPixelColorHsv
is explained at the end of this article.
#include <Adafruit_NeoPixel.h>
#include "hsv.h"
// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
strip.begin();
// hue - whole circle
// saturation - maximum
// value - very low
for (int i = 0; i < CNT; i++)
strip.setPixelColor(i, getPixelColorHsv(i, i * (MAXHUE / CNT), 255, 10));
strip.show();
}
void loop() {
}
We will use two examples. In one, we set the NeoPixel Ring to a different saturation of red color. In the second example, we adjust the saturation with the gamma correction.
Saturation of color without gamma correction occurs along the NeoPixel Ring circuit so that the red color changes very quickly to white and the human eye can not recognize any differences in the remaining pixels.
#include <Adafruit_NeoPixel.h>
#include "hsv.h"
// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
strip.begin();
// hue - red
// saturation - 255-0
// value - low
for (int i = 0; i < CNT; i++)
strip.setPixelColor(i, getPixelColorHsv(i, 0, 255-i*(255/CNT), 100));
strip.show();
}
void loop() {
}
With the gamma correction, the result is significantly better and the human eye sees a gradual change from red to white across the NeoPixel Ring circuit.
#include <Adafruit_NeoPixel.h>
#include "hsv.h"
// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
strip.begin();
// hue - red
// saturation - 255-0
// value - low
for (int i = 0; i < CNT; i++)
strip.setPixelColor(i, getPixelColorHsv(i, 0, 255-strip.gamma8(i*(255/CNT)), 100));
strip.show();
}
void loop() {
}
We will use two examples. In one, we set the NeoPixel Ring to a different brightness of red color. In the second example, we adjust the brightness value by using the gamma correction.
Even in this case, the human eye has the problem of recognizing changes in brightness. The NeoPixel Ring color changes very quickly from black to red and can not see any change on most pixels.
#include <Adafruit_NeoPixel.h>
#include "hsv.h"
// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
strip.begin();
// hue - red
// saturation - max
// value - 0-255
for (int i = 0; i < CNT; i++)
strip.setPixelColor(i, getPixelColorHsv(i, 0, 255, i*(255/CNT)));
strip.show();
}
void loop() {
}
With the gamma correction, we achieve optimal brightness distribution across the NeoPixel Ring circuit.
#include <Adafruit_NeoPixel.h>
#include "hsv.h"
// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6
Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
strip.begin();
// hue - red
// saturation - max
// value - 0-255
for (int i = 0; i < CNT; i++)
strip.setPixelColor(i, getPixelColorHsv(i, 0, 255, strip.gamma8(i*(255/CNT))));
strip.show();
}
void loop() {
}
This function is used to convert color between HSV and RGB. Fortunately I did not have to write it because I found it in the Pull request
for the NeoPixel library. At the time of writing examples, this function was not included in the library. That's why I've modified this function so I can use it with my examples. The function code is not easy to understand, but the author has done a great deal of work. The function is written in such a way that all calculations are integer numbers. It is necessary to approach it as a black box that does what it is intended for.
uint32_t getPixelColorHsv(
uint16_t n, uint16_t h, uint8_t s, uint8_t v) {
uint8_t r, g, b;
if (!s) {
// Monochromatic, all components are V
r = g = b = v;
} else {
uint8_t sextant = h >> 8;
if (sextant > 5)
sextant = 5; // Limit hue sextants to defined space
g = v; // Top level
// Perform actual calculations
/*
Bottom level:
--> (v * (255 - s) + error_corr + 1) / 256
*/
uint16_t ww; // Intermediate result
ww = v * (uint8_t)(~s);
ww += 1; // Error correction
ww += ww >> 8; // Error correction
b = ww >> 8;
uint8_t h_fraction = h & 0xff; // Position within sextant
uint32_t d; // Intermediate result
if (!(sextant & 1)) {
// r = ...slope_up...
// --> r = (v * ((255 << 8) - s * (256 - h)) + error_corr1 + error_corr2) / 65536
d = v * (uint32_t)(0xff00 - (uint16_t)(s * (256 - h_fraction)));
d += d >> 8; // Error correction
d += v; // Error correction
r = d >> 16;
} else {
// r = ...slope_down...
// --> r = (v * ((255 << 8) - s * h) + error_corr1 + error_corr2) / 65536
d = v * (uint32_t)(0xff00 - (uint16_t)(s * h_fraction));
d += d >> 8; // Error correction
d += v; // Error correction
r = d >> 16;
}
// Swap RGB values according to sextant. This is done in reverse order with
// respect to the original because the swaps are done after the
// assignments.
if (!(sextant & 6)) {
if (!(sextant & 1)) {
uint8_t tmp = r;
r = g;
g = tmp;
}
} else {
if (sextant & 1) {
uint8_t tmp = r;
r = g;
g = tmp;
}
}
if (sextant & 4) {
uint8_t tmp = g;
g = b;
b = tmp;
}
if (sextant & 2) {
uint8_t tmp = r;
r = b;
b = tmp;
}
}
return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b;
}
The source code is located on the GitHub server.
16.04.2018