NeoPixel Ring HSV - Nekonečný příběh

Zápisník experimentátora

Hierarchy: WS2812

Předchozí články (01, 2) a videa o používání barevného modelu HSV (hue, saturation, value) ukázaly, že čtenáři mají stále problém nastavit správnou hodnotu tak, aby získali požadovanou barvu na rotujícím prstenci. V tomto článku se to pokusíme vyřešit. Pomocí dvou tlačítek a OLED displeje nastavíme vhodnou barvu a její hodnotu si přečteme přímo na displeji.

V komentářích k videu nacházím pravidelně takovouto otázku. Ta červená barva je pěkná, ale jak to mám udělat, aby tam rotovala modrá barva? Nebo zelená barva? Nebo žlutá barva? Jednou jsem se to už pokusil vyřešit a udělal jsem ještě jedno video, ve kterém jsem používal tři potenciometry pro nastavení HSV a rychlosti rotace. Nepomohlo to. Otázky se stále opakovaly.

Opět jsem si poskládal na breadboarde původní zapojení elektrického obvodu, upravil jsem zdrojový kód o výpisy na sériový port a k videu jsem dopsal tabulku hodnot HUE. A když jsem tu tabulku doplnil pod video, napadla mě myšlenka. Co je nejjednodušší ovládací prvek k Arduinu? Tlačítko. Jakékoliv čtení výpisu na sériovém portu je zjevně pro mnohé čtenáře komplikované. Ale tlačítka používáme každý den. Ať už si jimi nastavujeme čas ohřívání v mikrovlnce, nebo si jen nastavujeme čas na hodinách, jsme naučeni používat dvě tlačítka. Jedno na zvýšení hodnoty a druhé na její snížení. A tak jsem vymyslel tento příklad se dvěma tlačítka a jedním OLED displejem, na kterém si každý může přečíst aktuální hodnotu HUE, kterou vidí rotovat na prstenci.

Použité súčiastky

  • Arduino Uno {linkArduino} - Můžete použít libovolnou verzi Arduina.
  • Breadboard {linkBreadboard}
  • NeoPixel Ring 24 LED {linkNeopixel} - Obsahuje 24 RGB LED diod s čipem WS2812B.
  • Rezistor 1k {linkR} - Rezistor se připojí na datový vstup NeoPixel Ringu. Slouží jako ochrana pinu.
  • Kondenzátor 220 uF {linkC} - Kondenzátor se připojí mezi VCC a GND na NeoPixel Ringu. Slouží k vyrovnávání napěťových špiček.
  • Tlačidlo {linkButton} - Potřebujeme dvě tlačítka. Jedním tlačítkem budeme zvyšovat hodnotu HUE a druhým tlačítkem ji budeme snižovat.
  • OLED {linkOled} - Na displeji se bude zobrazovat hodnota HUE.

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.

Použité knihovny

Použil jsem tyto knihovny. Obě knihovny se dají instalovat pomocí správce knihoven.

Příklad 1

Takto vypadá zdrojový kód k prvnímu příkladu. Nejprve několik definic konstant. NeoPixel Ring mám připojen k pinu 6. Použil jsem NeoPixel Ring s 24 LED diodami. Potom následuje několik globálních proměnných. Proměnné hue a oldhue používám na testování, zda se změnila hodnota HUE. Tehdy vypíšu aktuální hodnotu na displeji. SPI OLED displej používám na takových pinech, aby se dal využívat rychlý SPI přenos. Můžete použít i I2C displej, ale tehdy si musíte zvolit jiný konstruktor, který bude odpovídat vašemu displeji.

Tlačítka jsou zapojeny tak, aby používali interní pull-up rezistory v Arduinu. V tomto případě se nemusíme zabývat ošetřováním zákmity tlačítek, protože si jen periodicky kontroluji, zda je dané tlačítko stlačené a podle toho zvyšuji nebo snižuji hodnotu HUE. Displej překresluji pouze pokud se změní hodnota HUE. Na první pohled to není patrné v kódu, ale prokreslení displeje trvá několik milisekund a to má vliv na to, že při stisknutí tlačítka se animace na NeoPixel Ring mírně zpomalí. Je to nedokonalost, kterou se pokusím vyřešit v druhém příkladu.

#include <Adafruit_NeoPixel.h>
#include <U8g2lib.h>
#include "hsv.h"

// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6

int position = 0;
int hue = 0;
int oldhue = -1;
char output[128];

U8G2_SSD1306_128X64_NONAME_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  u8g2.begin();
  u8g2.setFont(u8g2_font_ncenB10_tr);
}

void loop() {
  // hue - red
  // saturation - max
  // value - 0-255
  for (int i = 0; i < CNT; i++) {
    strip.setPixelColor((i + position) % CNT, getPixelColorHsv(i, hue, 255, strip.gamma8(i * (255 / CNT))));
  }
  strip.show();

  position++;
  position %= CNT;

  if (digitalRead(2) == LOW) {
    hue += 1;
    hue %= MAXHUE;
  }
  if (digitalRead(3) == LOW) {
    hue -= 1;
    if (hue < 0)
      hue = MAXHUE - 1;
  }

  if (oldhue != hue) {
    oldhue = hue;
    sprintf(output, "hue: %d", hue);

    u8g2.firstPage();
    do {
      u8g2.drawStr(16, 32, output); // write something to the internal memory
      for (int j = 0; j <= 128; j += 2)
        u8g2.drawLine(j, 0, j, (j % 10 == 0 ? 2 : 1));
      for (int j = 0; j <= 64; j += 2)
        u8g2.drawLine(0, j, (j % 10 == 0 ? 2 : 1), j);
    } while ( u8g2.nextPage() );
  }

  delay(50);
}

Příklad 2

V tomto příkladu se pokusím upravit algoritmus prokreslení displeje tak, aby se při stisknutí tlačítka animace na NeoPixel Ring nezpomalila. Dá se to vyřešit několika způsoby. V tomto případě využiji to, že mezi jednotlivými posuny na NeoPixel Ring program čeká 50 ms. Odhadneme, kolik trvá vykreslení na displeji a tehdy budeme čekat kratší čas. Po několika pokusech jsem odhadl, že vykreslení trvá přibližně 40 ms a podle toho jsem upravil hodnotu v pomocné proměnné dly.

#include <Adafruit_NeoPixel.h>
#include <U8g2lib.h>
#include "hsv.h"

// data pin
#define PIN 6
// led count
#define CNT 24
// max Hue
#define MAXHUE 256*6

int position = 0;
int hue = 0;
int oldhue = -1;
char output[128];

U8G2_SSD1306_128X64_NONAME_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

Adafruit_NeoPixel strip = Adafruit_NeoPixel(CNT, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);
  u8g2.begin();
  u8g2.setFont(u8g2_font_ncenB10_tr);
}

void loop() {
  // hue - red
  // saturation - max
  // value - 0-255
  for (int i = 0; i < CNT; i++) {
    strip.setPixelColor((i + position) % CNT, getPixelColorHsv(i, hue, 255, strip.gamma8(i * (255 / CNT))));
  }
  strip.show();

  position++;
  position %= CNT;

  if (digitalRead(2) == LOW) {
    hue += 1;
    hue %= MAXHUE;
  }
  if (digitalRead(3) == LOW) {
    hue -= 1;
    if (hue < 0)
      hue = MAXHUE - 1;
  }

  int dly = 50;
  if (oldhue != hue) {
    dly = 10;
    oldhue = hue;
    sprintf(output, "hue: %d", hue);

    u8g2.firstPage();
    do {
      u8g2.drawStr(16, 32, output); // write something to the internal memory
      for (int j = 0; j <= 128; j += 2)
        u8g2.drawLine(j, 0, j, (j % 10 == 0 ? 2 : 1));
      for (int j = 0; j <= 64; j += 2)
        u8g2.drawLine(0, j, (j % 10 == 0 ? 2 : 1), j);
    } while ( u8g2.nextPage() );
  }

  delay(dly);
}

Zdrojový kód

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



Video


13.06.2019


Menu