NeoPixel Ring HSV test

Zápisník experimentátora

Hierarchy: WS2812

NeoPixel Ring obsahuje v každém bodě tři LED diody, které tvoří pixel. Výsledná barva se nastavuje pomocí tří složek RGB. Takové vytváření barev je pro člověka komplikované, protože ne každý si umí představit výslednou barvu, která se skládá ze tří složek. Pro usnadnění nastavování barev se používá HSV (hue, saturation, value) model, který tyto barvy míchá pomocí:

  • Hue - Barevný odstín.
  • Saturation - Sytost barvy. Projevuje se na modelu tak, jako kdyby jste do barvy přilévali bílou barvu.
  • Value - Hodnota jasu. Projevuje se na modelu tak, jako kdyby jste do barvy přilévali černou barvu.

V tomto článku se pokusíme pomocí HSV modelu nastavovat NeoPixel Ring. Pro každou složku HSV modelu si napíšeme vzorový program, který ji bude zobrazovat.

Použité součástky

  • Arduino Uno - Můžete použít libovolnou verzi Arduina.
  • NeoPixel Ring 24 LED (link) - Obsahuje 24 RGB LED diód s čipem WS2812B.
  • Rezistor 1k (link) - Rezistor se připojí na datový vstup NeoPixel Ringu. Slouží jako ochrana pinu.
  • Kondenzátor 220 uF (link) - Kondenzátor se připojí mezi VCC a GND na NeoPixel Ringu. Slouží k vyrovnávání napěťových špiček.

Ochranné součástky jsou nutné proto, aby se NeoPixel Ring nepoškodil. Podrobnosti ochrany jsou uvedeny v článku o napájení těchto diod.

Na obrázku je Ring s 12 diodami. Ring s 24 diodami se nijak mimořádně od něj neliší a je proto možné použít i menší ring po příslušné úpravě kódu.

Hue

V tomto příkladu se po obvodu NeoPixel Ring zobrazí jednotlivé barevné odstíny. Barevný odstín se nastavuje v intervalu 0-MAXHUE. Sytost je nastavena na maximální hodnotu a jas má minimální hodnotu, aby byly podmínky pro fotografování co nejlépe. Rozdělení barev na jednotlivé pixely je uděláno pomocí jednoduchého vzorce i * (MAXHUE / CNT). Funkce getPixelColorHsv je vysvětlena na konci tohoto článku.

#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() {
}

Saturation

Použijeme dva příklady. V jednom nastavíme po obvodu NeoPixel Ring různou sytost červené barvy. Ve druhém příkladu sytost nastavíme pomocí gama korekce.

Bez gama korekce

Sytost barvy bez gama korekce se projevuje po obvodu NeoPixel Ring tak, že se červená barva velmi rychle změní na bílou a lidské oko neví rozeznat žádné rozdíly na zbývajících pixelech.

#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() {
}

S gama korekcí

S gama korekcí je výsledek výrazně lepší a lidské oko vidí postupnou změnu z červené barvy na bílou barvu po celém obvodu NeoPixel Ring.

#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() {
}

Value

Použijeme dva příklady. V jednom nastavíme po obvodu NeoPixel Ring různou hodnotu jasu červené barvy. Ve druhém příkladu hodnotu jasu nastavíme pomocí gama korekce.

Bez gama korekce

I v tomto případě má lidské oko problém rozeznat změny jasu. Barva na NeoPixel Ring se velmi rychle změní z černé barvy na červenou barvu a na většině pixelů nevidí žádnou změnu.

#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() {
}

S gama korekcí

S gama korekcí dosáhneme optimální rozložení jasu po celém obvodu NeoPixel Ring.

#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() {
}

Funkce getPixelColorHsv

Tato funkce slouží pro převod barvy mezi modelem HSV a RGB. Naštěstí jsem ji nemusel napsat, protože jsem ji našel v Pull request pro knihovnu NeoPixel. V době psaní příkladů nebyla tato funkce zahrnuta v knihovně. Tuto funkci jsem proto mírně upravil, aby byla použitelná s mými příklady. Kód funkce není snadno pochopitelný, ale autor v ní udělal velký kus práce. Funkce je napsána tak, aby byly všechny výpočty s celými čísly. Třeba k ní přistupovat jako k černé skříňce, která dělá to, na co je určena.

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;
}

Zdrojový kód

Zdrojový kód se nachází na serveru GitHub.


16.04.2018


Menu