从一种颜色渐变到另一种颜色的Arduino RGB LED?

9

目前我已经成功让LED灯循环显示了八种颜色。除了我希望更自然的感觉,希望能够从一个颜色渐变到另一个颜色,而不是直接替换掉。

以下是我的代码:

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

LED是否与AVR端口的阴极相连?或者AnalogWrite()中的255-x是什么意思? - user529758
嗨!我正在使用阳极(+)RGB LED,这就是为什么“255 -”是必要的。 - KingPolygon
你需要的是Delta Sigma调制。你可以使用SW IO生成它,或者你可以使用带有它的微控制器,比如英飞凌XMC1000(BCCU)。http://www.infineon.ro/cms/en/product/microcontrollers/32-bit-industrial-microcontrollers-based-on-arm-registered-cortex-tm-m/32-bit-xmc4000-industrial-microcontrollers-arm-registered-cortex-tm-m4/xmc-development-tools-software-and-kits/xmc1000-starter-kits/boot-kit-xmc1100/channel.html?channel=db3a30433cfb5caa013d115dda020665 - joy
7个回答

13
其他答案忽略了这个话题的一个事实,即人类对光强度的感知是对数,而不是线性的analogWrite()例程设置输出引脚的PWM占空比,而这是线性的。 因此,通过将最小占空比(例如0)和最大占空比(为方便计算,假设为10)分成相等的块,您将以线性方式控制强度,这将不能给出令人满意的结果。
相反,您需要指数设置强度。 假设您的最大强度为255。 您可以将强度视为要提高某个数字的幂。 在我们的情况下,考虑到我们处理喜欢二进制的计算机,2的幂很方便。 那么,
2^0 =1
2^8=256

所以我们现在可以有8个亮度级别。请注意,我们的最小值现在不再是完全关闭(它是1而不是0),而我们的最大值超出了范围(256而不是255)。所以我们修改公式为:

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

output = 2 ^ intensity - 1
或者在代码中。
int output = 1<<intensity - 1;

这会产生从强度值 08 (包括)的范围内的0到255的值,因此实际上我们得到了九个强度级别。如果你想要更平滑的过渡(即更多的强度级别),并且仍然使用对数强度,你需要使用浮点数。

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


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

enter image description here

问题在于如何导航这个三角形:你可以沿着边走,从R到G再到B。这样你永远不会看到白色(所有通道完全打开)或“黑色”(所有通道完全关闭)。你可以将你的颜色空间视为一个六边形,具有额外的紫色(R+B)、黄色(G+B)和棕色(R+G)颜色,也可以沿着周长进行导航(同样不包括白色或黑色)。有许多淡化的可能性,就像我们可能想到的其他图形一样。

当我构建这样的渐变程序时,我喜欢的颜色空间和遍历方式是:将每个通道视为一个二进制位,现在你有三个(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);     
    }
}

这个并没有真正尝试回答“颜色之间的淡出”问题(在我看来)。 - Martin Thompson
@MartinThompson:添加了一些代码来实现这个。我想这就是你要找的。 - angelatlarge
谢谢,有用的代码。虽然...我仍然认为使用代表颜色的颜色空间更有帮助 - 您的“六边形路线”似乎是绕着HSV类型空间的圆圈的近似。 - Martin Thompson
@Martin Thompson 我认为一个人如何浏览颜色空间的偏好取决于应用程序。我并不完全清楚 OP 试图实现什么,我认为这真的会影响一个人如何浏览颜色空间。 - angelatlarge
如果你有两种颜色需要淡化过渡,我的经验是在HSL领域中进行插值步骤比在RGB领域中进行更能产生更令人愉悦的(视觉上)结果。然而,正如你所说,这取决于最终目标是什么... - Martin Thompson

10

确实可以在不同的颜色之间进行渐变。我通常在Arduino书籍和网页上的代码中缺少的是,它可以在Arduino IDE中编写C++类。因此,我将展示一个使用C++类进行颜色渐变的示例。

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

为了让 LED 渐变,您需要减少/增加 PWM 值并等待一段时间。您需要稍等一会儿,否则 Arduino 将尝试每秒渐变/调暗数千次 LED,您将看不到渐变效果,尽管它可能存在。因此,您正在寻找一种方法逐渐减少/增加第二个参数以控制三个 LED 的 analogWrite();有关更详细的说明,请参见 Arduino Cookbook 的第 7 章。该书对于 Arduino 爱好者来说是一本不错的读物!
我将代码从原始帖子中改编,包含了一个“rgb_color”类,它更多地只是红、绿和蓝值的容器。但更重要的是fader类。当构造fader实例时,应在构造函数中正确指定红、绿和蓝引脚。然后,fader包含一个成员函数void fade(const rgb_color& const rgb_color&),该函数将在输入颜色和输出颜色之间进行渐变。默认情况下,该函数将从输入颜色到输出颜色采用256步长为10ms的渐变。(请注意,由于整数除法,这并不意味着每一步都是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);
}

5
这可能是您正在寻找的内容。每当我们想要在光谱上移动颜色并以圆形和平滑的方式过渡颜色时,我们实际上是在使用HSI / HSV(色调,饱和度,强度/值)颜色空间中的色调来移动光线。请看下面的图示:

enter image description here

我们将为色调附加一个0-360的值,因为色调有360个色彩度。 饱和度的值为0.00 - 1.00,强度/值的值为0.00 - 1.00。
这是我在MEGA 2560上的电路图: enter image description here 这是这段代码运行的视频:

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

因此,让我们构建一个函数,我们可以传递色调值和一个循环,在我们的循环函数内部调用该值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);
  }


}

4

如果您想要在颜色之间淡入淡出,建议使用易于操作的颜色空间进行工作,然后在最后转换回RGB。

例如,可以使用HSL颜色空间,保持S和L不变(比如全饱和度和高亮度的颜色),然后让H在圆周上变化——您将从红色经过绿色、蓝色,再回到红色。然后将其转换回RGB,并使用这些值来控制LED驱动器。我曾在一个“情绪灯”应用中使用这种技术,而其他关于颜色空间转换的代码可以在Stack Overflow找到。


1
您可以使用结构体来简化您的颜色代码。
struct Color
{
    unsigned char r;
    unsigned char g;
    unsigned char b;
};

然后,很容易实现淡入淡出函数。
// 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);
    }        
}

3
这真的回答了“褪色”问题吗? - Martin Thompson
@ogni42:使用示例? - fulvio

1

我希望能提供更加用户友好的答案,以便更易理解其工作原理。

在下面的示例中,我使用了共阳极RGB LED。


但在我的项目中:为了给RGB LED设置颜色,我通过硬件串口发送一个字符串。

命令示例:RGB000255000

这个字符串命令被分成了4部分,每部分包含3个字符。 以上述命令示例为例:

  • "RGB":用于过滤命令将在哪里执行。
  • "000":第2个3字符表示红色值。
  • "255":第3个3字符表示绿色值。
  • "000":第4个3字符表示蓝色值。

结果:这将在你的LED上输出绿色。


请参见下面的代码:

// Set your LED Pins.
const int rPin = 9;
const int gPin = 10;
const int bPin = 11;

// Set the variables that will assign a value to each Color Pin.
int rVal = 255;
int gVal = 255;
int bVal = 255;

// Fade Red Pin (In / Out).
void FadeRed(int red)
{
  // When Red Value on Red Pin is Inferior to the New Value: Fade In.
  if (rVal < red)
  {
    // Fade Out.
    for (int r = rVal; r <= red; r++)
    {
      // Set the Variable and Pin values.
      rVal = r;
      analogWrite(rPin, rVal);

      // Delay Slighlty (Synchronously). For Asynchronous Delay; you may try using "millis".
      delay(6);
    } 
  }

  // When Red Value on Red Pin is Superior to the New Value: Fade Out.
  else
  { 
    for (int r = rVal; r >= red; r--)
    {
      rVal = r;
      analogWrite(rPin, rVal);
      delay(6); 
    }
  }
}

// Fade Green Pin (In / Out).
void FadeGreen(int green)
{
  if (gVal < green)
  {
    for (int g = gVal; g <= green; g++)
    {
      gVal = g;
      analogWrite(gPin, gVal);
      delay(6);
    }
  }
  
  else
  { 
    for (int g = gVal; g >= green; g--)
    { 
      gVal = g;
      analogWrite(gPin, gVal);
      delay(6);
    }
  }
}

// Fade Blue Pin (In / Out).
void FadeBlue(int blue)
{
  if (bVal < blue)
  {
    for (int b = bVal; b <= blue; b++)
    {
      bVal = b;
      delay(6);
      analogWrite(bPin, b);
    }
  }
  
  else
  {
    for (int b = bVal; b >= blue; b--)
    {
      bVal = b;
      delay(6);
      analogWrite(bPin, b);
    }
  }
}

void FadeColor(int red, int green, int blue)
{
    // Debug Only.
    Serial.println("\n[+] Received Values");
    Serial.println(red);
    Serial.println(green);
    Serial.println(blue);

    // Code.
    FadeRed(red);
    FadeGreen(green);
    FadeBlue(blue);

    // Debug Only.
    Serial.println("\n[+] Pin Values \n");
    Serial.println(rVal);
    Serial.println(gVal);
    Serial.println(bVal);
}

/* Set RGB LED Color According to String Value. (i.e: RGB000000000) */
void SetColor(String color)
{  
  // Retrieve the New Color from String.
  /* Split a String : Start Position; End Position */
  String red = color.substring(3, 6);   /* Get the 1st 3 Characters Corresponding to RED   */
  String green = color.substring(6, 9); /* Get the 2nd 3 Characters Corresponding to GREEN */
  String blue = color.substring(9, 12); /* Get the Last 3 Characters Corresponding to BLUE */

  int r = atoi(red.c_str());
  int g = atoi(green.c_str());
  int b = atoi(blue.c_str());

  int redVal = 255 - r;
  int grnVal = 255 - g;
  int bluVal = 255 - b;
  
  FadeColor(redVal, grnVal, bluVal);
}


void setup()
{  
  pinMode(rPin, OUTPUT);
  pinMode(gPin, OUTPUT);
  pinMode(bPin, OUTPUT);

  pinMode(rPin, HIGH);
  pinMode(gPin, HIGH);
  pinMode(bPin, HIGH);

  analogWrite(rPin, rVal);
  analogWrite(gPin, gVal);
  analogWrite(bPin, bVal);
}

0
这是两个以uint32_t存储的RGB值之间的快速线性淡入淡出,格式为0x00RRGGBB,在许多可寻址RGB像素带中使用,例如NeoPixel(并受到NeoPixel Arduino库中某些代码的启发)。
它不考虑颜色空间,但在实践中仍然看起来漂亮而平滑。
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]);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接