I'm trying to make a smooth transition with arduino, it receives rgb values from serial correctly, but it seems to don't reach the target color because it oscillates between different values. I'm using this library to convert in hsv RGBConverter. I tried to convert rgb value in hsv and to change gradually hsv values. This is my code:
RGBConverter converter = RGBConverter();
my_color cur_color;
void setup() {
pinMode(RED_LED, OUTPUT);
pinMode(GREEN_LED, OUTPUT);
pinMode(BLUE_LED, OUTPUT);
Serial.begin(BAUD_RATE);
cur_color={0,0,0};
}
void loop() {
my_color final={Serial.read(),Serial.read(),Serial.read()};
double hsv[3];
converter.rgbToHsv(final.red,final.green,final.blue,hsv);
reach_color(&cur_color,hsv);
}
Color is a struct that contains three byte values named red, green and blue
This is the reach_color method
void reach_color(struct color *start, double hsv_final[]){
double hsv[3];
byte rgb[3];
while(true){
converter.rgbToHsv(start->red,start->green,start->blue,hsv);
if ( hsv[0] >hsv_final[0]&&hsv[0]>0 ) hsv[0] -= 0.01;
else if(hsv[0]<hsv_final[0]&&hsv[0]<1) hsv[0]+= 0.01;
else if(hsv[1]>hsv_final[1]&&hsv[1]>0) hsv[1]-= 0.01;
else if(hsv[1]<hsv_final[1]&&hsv[1]<1) hsv[1]+= 0.01;
else if(hsv[2]>hsv_final[2]&&hsv[2]>0) hsv[2]-= 0.01;
else if(hsv[2]<hsv_final[2]&&hsv[2]<1) hsv[2]+= 0.01;
else return;
converter.hsvToRgb(hsv[0],hsv[1], hsv[2], rgb);
start->red=rgb[0];
start->green=rgb[1];
start->blue=rgb[2];
display_color(*start);
delay(30);
}
}
and this is display method
void display_color(struct color c){
analogWrite(GREEN_LED, c.green);
analogWrite(BLUE_LED, c.blue);
analogWrite(RED_LED, c.red);
}
I need to gradually move from current color to final color, but while never goes in the else clause and current color oscillates between different values, does anyone know where is the error? Thank you all in advance for the help
I think that my error is inside the algorithm reach_color
Right. Consider e. g. hsv[0]
is 0.005 and hsv_final[0]
is 0; the line
if ( hsv[0] >hsv_final[0]&&hsv[0]>0 ) hsv[0] -= 0.01;
will set hsv[0]
to −0.005 (which is outside the range [0, 1] assumed by hsvToRgb()
, whatever that does with it). In the next loop cycle, the line
else if(hsv[0]<hsv_final[0]&&hsv[0]<1) hsv[0]+= 0.01;
will set hsv[0]
to +0.005 anew - and so it goes on oscillating. In general, the repeated addition or subtraction of 0.01 is unlikely to yield exactly the same value that rgbToHsv()
returned.
Also you should be aware that 0.01 like most decimal fractions cannot be exactly represented in a double
(it results in approximately 0.01000000000000000021).
Moreover, the repeated calling of rgbToHsv()
in the loop, recalculating hsv[]
values based on the previous rgb[]
values, makes the algorithm's behavior more difficult to predict.
So, we have to allow for a tolerance of the HSV values for the comparisons, and better use only the hsv[]
values for the iteration:
void reach_color(struct color *start, double hsv_final[])
{
double hsv[3];
byte rgb[3];
converter.rgbToHsv(start->red, start->green, start->blue, hsv);
while (true)
{
if (hsv[0] - 0.01 >= hsv_final[0]) hsv[0] -= 0.01;
else if (hsv[0] + 0.01 <= hsv_final[0]) hsv[0] += 0.01;
else if (hsv[1] - 0.01 >= hsv_final[1]) hsv[1] -= 0.01;
else if (hsv[1] + 0.01 <= hsv_final[1]) hsv[1] += 0.01;
else if (hsv[2] - 0.01 >= hsv_final[2]) hsv[2] -= 0.01;
else if (hsv[2] + 0.01 <= hsv_final[2]) hsv[2] += 0.01;
else return;
converter.hsvToRgb(hsv[0], hsv[1], hsv[2], rgb);
start->red = rgb[0];
start->green = rgb[1];
start->blue = rgb[2];
display_color(*start);
delay(30);
}
}