C++ Arduino RGB LED 从一种颜色淡入另一种颜色?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15803986/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:46:56  来源:igfitidea点击:

Fading Arduino RGB LED from one color to the other?

c++colorsarduinorgbled

提问by KingPolygon

I've currently managed to get my LED to cycle through eight colors that I've selected. Everything is working correctly, except that I want to go for a more natural feel, and would like to fade / transition from one color to the next, instead of having them just replace one another.

我目前已经设法让我的 LED 循环显示我选择的八种颜色。一切正常,除了我想要更自然的感觉,并希望从一种颜色淡入淡出/过渡到另一种颜色,而不是让它们相互替换。

Here's my code so far:

到目前为止,这是我的代码:

int redPin = 11;
int greenPin = 10;
int bluePin = 9;

void setup()
{
    pinMode(redPin, OUTPUT);
    pinMode(greenPin, OUTPUT);
    pinMode(bluePin, OUTPUT);
}

void loop()
{
    setColor(250, 105, 0);   // Yellow
    delay(1000);

    setColor(250, 40, 0);    // Orange
    delay(1000);

    setColor(255, 0, 0);     // Red
    delay(1000);

    setColor(10, 10, 255);   // Blue
    delay(1000);

    setColor(255, 0, 100);   // Pink
    delay(1000);

    setColor(200, 0, 255);   // Purple
    delay(1000);

    setColor(0, 255, 0);     // Green
    delay(1000);

    setColor(255, 255, 255); // White
    delay(1000);
}

void setColor(int red, int green, int blue)
{
    analogWrite(redPin, 255-red);
    analogWrite(greenPin, 255-green);
    analogWrite(bluePin, 255-blue);
}

回答by angelatlarge

What the other answers omit about this topic is the fact that that human perception of light intensity is logarithmic, not linear. The analogWrite()routines are setting the output pin's PWMduty cycle, and are linear. So by taking the minimum duty cycle (say 0) and maximum duty cycle (say, for the sake of easy math this is 10) and dividing it into equal chunks, you will be controlling the intensitiy linearlywhich will not give satisfying results.

关于这个主题的其他答案忽略的是这样一个事实,即人类对光强度的感知是对数的,而不是线性的。该analogWrite()程序是设置输出引脚的PWM占空比,并是线性的。因此,通过采用最小占空比(比如0)和最大占空比(比如,为了简单的数学计算,这是10)并将其划分为相等的块,您将线性控制强度,这不会给出令人满意的结果。

What you need to do instead is set your intensity exponentially. Let's say your maximum intensity is 255. You can generate this result by treating your intensity as a power to raise some number to. In our case, given that we are dealing with computers that like binary, powers of two are convenient. So,

你需要做的是以指数方式设置你的强度。假设您的最大强度是255。您可以通过将您的强度视为提高某个数字的能力来产生此结果。在我们的例子中,考虑到我们正在处理喜欢二进制的计算机,二的幂很方便。所以,

2^0 =1
2^8=256

so we can have 8 intensity levels. Actually, note that out minimum is now not fully off (it is 1not 0) and our maximum is out of range (256not 255). So we modify the formula to be

所以我们可以有 8 个强度级别。实际上,请注意 out minimum 现在还没有完全关闭(1不是0),而我们的最大值超出了范围(256not 255)。所以我们修改公式为

output = 2 ^ intensity - 1
output = 2 ^ intensity - 1

Or in code

或者在代码中

int output = 1<<intensity - 1;

This yields values from 0 to 255 for intensity levels from 0to 8(inclusive), so we actually get nine levels of intensity. If you wanted smoother transitions (i.e. more levels of intensity), and still use logarithmic intensity you'll need floating-point math.

这产生值从0到255的强度级别从08(含),所以我们实际上得到九个级别的强度。如果您想要更平滑的过渡(即更多级别的强度),并且仍然使用对数强度,您将需要浮点数学。

If you apply this method of calculating intensity to each channel (R, G, B) then your perception will be in accord with what your code says it should be.

如果您将这种计算强度的方法应用于每个通道(R、G、B),那么您的感知将与您的代码所说的一致。



As fars as how to smoothly transition between various colors, the answer depends on how you want to navigate the color space. The simplest thing to do is to think about your color space as a triangle, with R, G, and B, as the verteces:

至于如何在各种颜色之间平滑过渡,答案取决于您想要如何导航色彩空间。最简单的做法是将您的色彩空间视为一个三角形,其中 R、G 和 B 作为顶点:

enter image description here

在此处输入图片说明

The question then is how to navigate this triangle: you could go along the sides, from R, to G, to B. This way you will never see white (all channels fully on) or "black" (all fully off). You could think of your color space as a hexagon, with additional purple (R+B), yellow (G+B), and brown (R+G) colors, and also navigate the perimeter (again, no white or black). There are as many fading possibilities as there are ways of navigating insides these, and other figures we might think of.

那么问题是如何导航这个三角形:你可以沿着边走,从 R,到 G,再到 B。这样你就永远不会看到白色(所有通道完全打开)或“黑色”(全部关闭)。您可以将您的色彩空间想象为一个六边形,带有额外的紫色 (R+B)、黄色 (G+B) 和棕色 (R+G) 颜色,并且还可以导航周边(同样,没有白色或黑色)。在这些以及我们可能会想到的其他数字中,有多少种方法可以在其中导航,就有多少逐渐消失的可能性。

When I built fading programs like this the color space and the traversal I liked was as follows: think of each channel as a binary bit, so now you have three (R, G, and B). If you think of each color as having some combination of these channels being fully on, you get 7 total colors (excluding black, but including white). Take the first of these colors, fade to it from black and back to black, and then go to the next color. Here's some code that does something like that:

当我构建这样的衰落程序时,我喜欢的颜色空间和遍历如下:将每个通道视为一个二进制位,所以现在你有三个(R、G 和 B)。如果您将每种颜色视为完全打开这些通道的某种组合,您将获得 7 种颜色(不包括黑色,但包括白色)。取这些颜色中的第一种,从黑色渐变到黑色,然后转到下一种颜色。这是一些执行类似操作的代码:

int targetColor = 1;
int nIntensity = 0;
int nDirection = 1;         // When direction is 1 we fade towards the color (fade IN)
                            // when 0 we fade towards black (fade OUT)
#define MAX_INTENSITY 8
#define MIN_INTENSITY 0
#define MAX_TARGETCOLOR 7

void loop() {
    for (;;) {

        // Update the intensity value
        if (nDirection) {
            // Direction is positive, fading towards the color
            if (++nIntensity >= MAX_INTENSITY) {
                // Maximum intensity reached
                nIntensity = MAX_INTENSITY;  // Just in case
                nDirection = 0;             // Now going to fade OUT
            } // else : nothing to do
        } else {
            if (--nIntensity <= MIN_INTENSITY) {
                nIntensity = MIN_INTENSITY; // Just in case
                // When we get back to black, find the next target color
                if (++targetColor>MAX_TARGETCOLOR) 
                    targetColor=1;          // We'll skip fading in and out of black
                nDirection = 1;             // Now going to fade IN
            } // else: nothing to do
        }

        // Compute the colors
        int colors[3];
        for (int i=0;i<3;i++) {
            // If the corresponding bit in targetColor is set, it's part of the target color
            colors[i] = (targetColor & (1<<i)) ? (1<<nIntensity) -1 : 0;
        }

        // Set the color
        setColor(colors[0], colors[1], colors[2]);

        // Wait
        delay(100);     
    }
}

回答by hetepeperfan

It is indeed possible to fade between different colors. What I'm also usually missing in Arduino books and code on the web is, that it is possible to write C++ classes in Arduino IDE. Therefore, I'm going to show an example that fades between colors using C++ classes.

确实可以在不同颜色之间褪色。我在 Arduino 书籍和网络上的代码中通常还缺少的是,可以在 Arduino IDE 中编写 C++ 类。因此,我将展示一个使用 C++ 类在颜色之间淡入淡出的示例。

An issue that should be addressed is on which pins the analogWrite should be done to, because not all pins are capable of Pulse Width Modulation (PWM). On a Arduino device the pins that support PWM are denoted with a tilde '~'. The Arduino UNO has digital pins ~3, ~5, ~6, ~9, ~10 and ~11. And most Arduino use those pins for PWM, but check your device to be sure. You can create PWM on regular digital pins by switching your led on for 1ms and of for 1 ms this mimics 50% power on the LED. Or turn it on 3 ms and of 1 ms this mimics 75% power.

应该解决的一个问题是应该在哪些引脚上进行模拟写入,因为并非所有引脚都能够进行脉宽调制 ( PWM)。在 Arduino 设备上,支持 PWM 的引脚用波浪号“~”表示。Arduino UNO 具有数字引脚 ~3、~5、~6、~9、~10 和 ~11。大多数 Arduino 将这些引脚用于 PWM,但请检查您的设备以确保。您可以通过将 LED 打开 1 毫秒和 1 毫秒来在常规数字引脚上创建 PWM,这模拟了 LED 上的 50% 功率。或者将其打开 3 毫秒和 1 毫秒,这将模拟 75% 的功率。

In order to fade a LED you would have to reduce/increase the PWM value and wait a bit. Youl'll have to wait a little while, because otherwise the arduino tries to fade/dim leds thousands of times per second and you won't see a fade effect, although it probably there. So you are looking for a method to gradually reduce/increase the second parameter to analogWrite( )for three LEDs; For a more thorough explanation see for example chapter 7 of Arduino Cookbook. That book is a good read for Arduino fans anyway!

为了使 LED 褪色,您必须减少/增加 PWM 值并稍等片刻。您将不得不等待一段时间,因为否则 arduino 会每秒尝试使 LED 灯淡化/变暗数千次,您将看不到淡化效果,尽管它可能存在。因此,您正在寻找一种方法来逐渐减少/增加analogWrite( )三个 LED的第二个参数;有关更详尽的解释,请参见Arduino Cookbook 的第 7 章。无论如何,这本书对 Arduino 粉丝来说是一本好书!

So I adapted the code from the OP to contain a 'rgb_color' class that is more or less just a container for red, green and blue values. But more importantly is the fader class. When an instance of fader is constructed the proper pins should be in the constructor red, green and blue respectively. Than the fader contains a member function void fade( const rgb_color& const rgb_color&)which will do the fading between the in and out color. By default the function will take 256 steps of 10ms from the input color to the output color. (note here due to integer divisions this doesn't really mean that each step 1/256 th, but perceputally you won't notice it).

因此,我修改了 OP 中的代码以包含一个“rgb_color”类,该类或多或少只是红色、绿色和蓝色值的容器。但更重要的是推子类。当构造推子的实例时,正确的引脚应分别位于构造函数中的红色、绿色和蓝色。比推子包含一个成员函数void fade( const rgb_color& const rgb_color&),它将在输入和输出颜色之间进行淡入淡出。默认情况下,该函数将从输入颜色到输出颜色需要 256 步,每步 10 毫秒。(注意这里由于整数除法,这并不意味着每一步都是 1/256 次,但你不会注意到它)。

/*
 * LedBrightness sketch
 * controls the brightness of LEDs on "analog" (PWM) output ports.
 */

class rgb_color {

  private:
    int my_r;
    int my_g;
    int my_b;
  public:
    rgb_color (int red, int green, int blue)
      :
        my_r(red),
        my_g(green),
        my_b(blue)
    {
    }

    int r() const {return my_r;}
    int b() const {return my_b;}
    int g() const {return my_g;}
};

/*instances of fader can fade between two colors*/
class fader {

  private:
    int r_pin;
    int g_pin;
    int b_pin;

  public:
    /* construct the fader for the pins to manipulate.
     * make sure these are pins that support Pulse
     * width modulation (PWM), these are the digital pins
     * denoted with a tilde(~) common are ~3, ~5, ~6, ~9, ~10 
     * and ~11 but check this on your type of arduino. 
     */ 
    fader( int red_pin, int green_pin, int blue_pin)
      :
        r_pin(red_pin),
        g_pin(green_pin),
        b_pin(blue_pin)
    {
    }

    /*fade from rgb_in to rgb_out*/
    void fade( const rgb_color& in,
               const rgb_color& out,
               unsigned n_steps = 256,  //default take 256 steps
               unsigned time    = 10)   //wait 10 ms per step
    {
      int red_diff   = out.r() - in.r();
      int green_diff = out.g() - in.g();
      int blue_diff  = out.b() - in.b();
      for ( unsigned i = 0; i < n_steps; ++i){
        /* output is the color that is actually written to the pins
         * and output nicely fades from in to out.
         */
        rgb_color output ( in.r() + i * red_diff / n_steps,
                           in.g() + i * green_diff / n_steps,
                           in.b() + i * blue_diff/ n_steps);
        /*put the analog pins to the proper output.*/
        analogWrite( r_pin, output.r() );
        analogWrite( g_pin, output.g() );
        analogWrite( b_pin, output.b() );
        delay(time);
      }
    }

};

void setup()
{
  //pins driven by analogWrite do not need to be declared as outputs
}

void loop()
{
  fader f (3, 5, 6); //note OP uses 9 10 and 11
  /*colors*/
  rgb_color yellow( 250, 105,   0 );
  rgb_color orange( 250,  40,   0 );
  rgb_color red   ( 255,   0,   0 );
  rgb_color blue  (  10,  10, 255 );
  rgb_color pink  ( 255,   0, 100 );
  rgb_color purple( 200,   0, 255 );
  rgb_color green (   0, 255,   0 );
  rgb_color white ( 255, 255, 255 );

  /*fade colors*/
  f.fade( white, yellow);
  f.fade( yellow, orange);
  f.fade( orange, red);
  f.fade( red, blue);
  f.fade( blue, pink);
  f.fade( pink, purple);
  f.fade( purple, green);
  f.fade( green, white);
}

回答by Sm0k3Scr33n

This is probably what you are looking for. Whenever we want to shift color over the spectrum and trasition the colors in a circular and smooth motion, what we are really doing is shifting light using HUE in the HSI/HSV (Hue, Saturation, Intensity/Value) color space.

这可能就是您正在寻找的。每当我们想要在光谱上移动颜色并以圆形和平滑的运动移动颜色时,我们真正要做的是在 HSI/HSV(色相、饱和度、强度/值)颜色空间中使用 HUE 移动光。

Take if you will this figure:

如果你愿意,请看这个数字:

enter image description here

在此处输入图片说明

We will attach a value from 0-360 for hue because hue has 360 degrees of color. A value of 0.00 - 1.00 for saturation, and a value of 0.00 -1.00 for intensity/value

我们将为色调附加一个 0-360 的值,因为色调具有 360 度的颜色。饱和度值为 0.00 - 1.00,强度/值值为 0.00 -1.00

Here is my circuit on the MEGA 2560: enter image description here

这是我在 MEGA 2560 上的电路: 在此处输入图片说明

Here is video of this code running:

这是这段代码运行的视频:

<iframe width="560" height="315" src="https://www.youtube.com/embed/gGG-GndSKi0" frameborder="0" allowfullscreen></iframe>

So lets build a function that we can pass the hue value and a for loop inside our loop function to call that value 360 times to shift over the full rainbow of color.

因此,让我们构建一个函数,我们可以在循环函数中传递色调值和 for 循环来调用该值 360 次以在整个彩虹色上移动。

//Define the pins we will use with our rgb led
int redPin = 9;
int greenPin = 10;
int bluePin = 11;

//define that we are using common anode leds
#define COMMON_ANODE

void setup()
{
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
}

int rgb[3];
//Arduino has no prebuilt function for hsi to rgb so we make one:
void hsi_to_rgb(float H, float S, float I) {
  int r, g, b;
  if (H > 360) {
    H = H - 360;
  }
  // Serial.println("H: "+String(H));
  H = fmod(H, 360); // cycle H around to 0-360 degrees
  H = 3.14159 * H / (float)180; // Convert to radians.
  S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1]
  I = I > 0 ? (I < 1 ? I : 1) : 0;
  if (H < 2.09439) {
    r = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
    g = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
    b = 255 * I / 3 * (1 - S);
  } else if (H < 4.188787) {
    H = H - 2.09439;
    g = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
    b = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
    r = 255 * I / 3 * (1 - S);
  } else {
    H = H - 4.188787;
    b = 255 * I / 3 * (1 + S * cos(H) / cos(1.047196667 - H));
    r = 255 * I / 3 * (1 + S * (1 - cos(H) / cos(1.047196667 - H)));
    g = 255 * I / 3 * (1 - S);
  }
  rgb[0] = r;
  rgb[1] = g;
  rgb[2] = b;

}
void setColor(int red, int green, int blue)
{
  #ifdef COMMON_ANODE
    red = 255 - red;
    green = 255 - green;
    blue = 255 - blue;
  #endif
  analogWrite(redPin, red);
  analogWrite(greenPin, green);
  analogWrite(bluePin, blue);
}


///here we have our main loop and the for loop to shift color
void loop()
{
//the for loop counts to 360 and because its in our control loop it will run forever
// We will use int i to increment the actual desired color 
for (int i=0; i<=360;i++){
  hsi_to_rgb(i,1,1);
  setColor(rgb[0],rgb[1],rgb[2]);
  //Changing the delay() value in milliseconds will change how fast the
  //the light moves over the hue values 
  delay(5);
  }


}

回答by Martin Thompson

If you want to fade between colours, work in a colourspace which makes it easy and then convert back to RGB at the end.

如果您想在颜色之间淡入淡出,请在颜色空间中工作,这样可以轻松完成,然后在最后转换回 RGB。

For example, work in HSL colour space, keep S and L constant (say a fully saturated and bright colour) and then "fade" H around the circle - you'll go from red through green, blue and back to red. Convert back to RGB and then use those values for your LED drives. I used this technique for a "mood lamp" app, and other code for the colour space conversioncan be found on SO.

例如,在HSL 色彩空间中工作,保持 S 和 L 恒定(例如完全饱和且明亮的颜色),然后在圆圈周围“淡化”H - 您将从红色通过绿色、蓝色再回到红色。转换回 RGB,然后将这些值用于 LED 驱动器。我将此技术用于“情绪灯”应用程序,并且 可以在 SO 上找到用于颜色空间转换的其他代码

回答by ogni42

You can simplify your code by using a struct for your color.

您可以通过为您的颜色使用结构来简化您的代码。

struct Color
{
    unsigned char r;
    unsigned char g;
    unsigned char b;
};

Then, it is easy to have a fading function

然后,很容易有一个衰落功能

// the old color will be modified, hence it is not given as reference
void fade(Color old, const Color& newColor)
{
    // get the direction of increment first (count up or down)
    // each of the inc_x will be either 1 or -1
    char inc_r = (newColor.r - old.r)/abs(newColor.r-old.r); // note, that the right hand side will be sign extended to int according to the standard.
    char inc_g = (newColor.g - old.g)/abs(newColor.g-old.g);
    char inc_b = (newColor.g - old.g)/abs(newColor.g-old.g);

    fadeOneColor(old.r, newColor.r, inc_r, old);
    fadeOneColor(old.g, newColor.g, inc_g, old);
    fadeOneColor(old.b, newColor.b, inc_b, old);
}

void fadeOneColor( unsigned char& col_old, 
                   const unsigned char& col_new, 
                   const char inc, 
                   Color& col)
{
    while(col_old != col_new)
    {
        col_old += inc;
        SetColor(col); 
        delay(20);
    }        
}

回答by Tim MB

Here's a fast linear fade between two RGB values stored in uint32_tas 0x00RRGGBBas is used in many addressable RGB pixel strips such as in NeoPixel (and is inspired by some of the code in the NeoPixel Arduino library).

下面是存储在两个RGB值之间的快速线性褪色uint32_t0x00RRGGBB在许多可寻址的RGB像素被用作条如在NeoPixel(并且由一些在NeoPixel Arduino的库中的代码的启发)。

It doesn't take colour space into consideration but still looks nice and smooth in practice.

它没有考虑色彩空间,但在实践中看起来仍然很漂亮和流畅。

uint32_t fadeColor(uint32_t const x, uint32_t const y, uint8_t const fade)
{
  // boundary cases don't work with bitwise stuff below
  if (fade == 0)
  {
    return x;
  }
  else if (fade == 255)
  {
    return y;
  }

  uint16_t const invFadeMod = (255 - fade) + 1;
  uint16_t const fadeMod = fade + 1;
  // overflows below to give right result in significant byte
  uint8_t const xx[3] // r g b
  {
    static_cast<uint8_t>((uint8_t(x >> 16) * invFadeMod) >> 8),
    static_cast<uint8_t>((uint8_t(x >> 8) * invFadeMod) >> 8),
    static_cast<uint8_t>((uint8_t(x >> 0) * invFadeMod) >> 8),
  };
  uint8_t const yy[3] // r g b
  {
    static_cast<uint8_t>((uint8_t(y >> 16) * fadeMod) >> 8),
    static_cast<uint8_t>((uint8_t(y >> 8)* fadeMod) >> 8),
    static_cast<uint8_t>((uint8_t(y >> 0)* fadeMod) >> 8),
  };
  return ((uint32_t)(xx[0] + yy[0]) << 16) | ((uint32_t)(xx[1] + yy[1]) <<  8) | (xx[2] + yy[2]);
}