NeoPixel Ring HSV test

Zápisník experimentátora

Hierarchy: WS2812

NeoPixel Ring obsahuje v každom bode tri LED diódy, ktoré tvoria pixel. Výsledná farba sa nastavuje pomocou troch zložiek RGB. Takéto vytváranie farieb je pre človeka komplikované, pretože nie každý si vie predstaviť výslednú farbu, ktorá sa skladá z troch zložiek. Na uľahčenie nastavovania farieb sa používa HSV (hue, saturation, value) model, ktorý tieto farby mieša pomocou:

  • Hue - Farebný odtieň.
  • Saturation - Sýtosť farby. Prejavuje sa na modeli tak, ako keby ste do farby prilievali bielu farbu.
  • Value - Hodnota jasu. Prejavuje sa na modeli tak, ako keby ste do farby prilievali čiernu farbu.

V tomto článku sa pokúsime pomocou HSV modelu nastavovať NeoPixel Ring. Pre každú zložku HSV modelu si napíšeme vzorový program, ktorý ju bude zobrazovať.

Použité súčiastky

  • Arduino Uno {linkArduino} - Môžete použiť ľubovoľnú verziu Arduina.
  • Breadboard {linkBreadboard}
  • NeoPixel Ring 24 LED {linkNeopixel} - Obsahuje 24 RGB LED diód s čipom WS2812B.
  • Rezistor 1k {linkR} - Rezistor sa pripojí na dátový vstup NeoPixel Ringu. Slúži ako ochrana pinu.
  • Kondenzátor 220 uF {linkC} - Kondenzátor sa pripojí medzi VCC a GND na NeoPixel Ringu. Slúži na vyrovnávanie napäťových špičiek.

Ochranné súčiastky sú nutné preto, aby sa NeoPixel Ring nepoškodil. Podrobnosti ochrany sú uvedené v článku o napájaní týchto diód.

Na obrázku je Ring s 12 diódami. Ring s 24 diódami sa nijako mimoriadne od neho nelíši a je preto možné použiť aj menší ring po príslušnej úprave kódu.

Hue

V tomto príklade sa po obvode NeoPixel Ring zobrazia jednotlivé farebné odtiene. Farebný odtieň sa nastavuje v intervale 0-MAXHUE. Sýtosť je nastavená na maximálnu hodnotu a jas má minimálnu hodnotu, aby boli podmienky pre fotografovanie čo najlepšie. Rozdelenie farieb na jednotlivé pixely je urobené pomocou jednoduchého vzorca i * (MAXHUE / CNT). Funkcia getPixelColorHsv je vysvetlená na konci tohto č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 príklady. V jednom nastavíme po obvode NeoPixel Ring rôznu sýtosť červenej farby. V druhom príklade sýtosť nastavíme pomocou gama korekcie.

Bez gama korekcie

Sýtosť farby bez gama korekcie sa prejavuje po obvode NeoPixel Ring tak, že sa červená farba veľmi rýchlo zmení na bielu a ľudské oko nevie rozoznať žiadne rozdiely na zvyšných pixeloch.

#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 korekciou

S gama korekciou je výsledok výrazne lepší a ľudské oko vidí postupnú zmenu z červenej farby na bielu farbu po celom obvode 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 príklady. V jednom nastavíme po obvode NeoPixel Ring rôznu hodnotu jasu červenej farby. V druhom príklade hodnotu jasu nastavíme pomocou gama korekcie.

Bez gama korekcie

Aj v tomto prípade má ľudské oko problém rozoznať zmeny jasu. Farba na NeoPixel Ring sa veľmi rýchlo zmení z čiernej farby na červenú farbu a na väčšine pixelov nevidí žiadnu zmenu.

#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 korekciou

S gama korekciou dosiahneme optimálne rozloženie jasu po celom obvode 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() {
}

Funkcia getPixelColorHsv

Táto funkcia slúži na prevod farby medzi modelom HSV a RGB. Našťastie som ju nemusel napísať, pretože som ju našiel v Pull request pre knižnicu NeoPixel. V čase písania príkladov nebola táto funkcia zahrnutá v knižnici. Túto funkciu som preto mierne upravil, aby bola použiteľná s mojimi príkladmi. Kód funkcie nie je ľahko pochopiteľný, ale autor v nej urobil veľký kus práce. Funkcia je napísaná tak, aby boli všetky výpočty s celými číslami. Treba k nej pristupovať ako k čiernej skrinke, ktorá robí to, na čo je určená.

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 sa nachádza na serveri GitHub.


15.04.2018


Menu