HSI to RGB/RGBW conversion library with optional direct PWM output

Dependents:   KL25Z_HSI2RGBW_PWM KL25Z_HSI2RGBW_PWM_local KL25Z_FFT_Demo FFT_BUENA ... more

HSI to RGB / RGBW with optional PWM output

Library for converting HSI color space values to RGB or RGBW and, optionally, directly send the converted values to PWM outputs.

Code ported from http://saikoled.com - Copyright 2013, Brian Neltner
http://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white

Info on the HSI color space can be found here and here.

KL25Z PTA4 as PWM output

By default, PTA4 is defined as the NMI input in the the mbed library. When PTA4 is to be used as a PWM output, we MUST disable the NMI assignment, otherwise the system will lock-up when this pin is pulled low. Read this page for more information.
The easiest way (without changing mbed-src) to disable the NMI input is by adding following code at the start of your main program (this is present in the demo program mentioned below):

// Dummy ISR for disabling NMI on PTA4 - !! DO NOT REMOVE THIS !!
extern "C" void NMI_Handler() {
    DigitalIn test(PTA4);
}

Demo code using the KL25Z-FRDM board:

Import programKL25Z_HSI2RGBW_PWM

SaikoLED fade demo using the HSI2RGBW_PWM libary


Constructor & conversion function
When 3 PWM ouptuts are declared, the library automatically selects RGB mode.
When 4 PWM ouptuts are declared, the library automatically selects RGBW mode.
When no PWM outputs are declared, the library defaults to RGBW mode. Use .colorMode(RGB) to switch to RGB mode.
depending on the way you want to use this library, you can call the conversion function in different ways

// Assign 4 PWM outputs and also allow to return the converted RGBW value.
hsi2rgbw_pwm led(PTD4, PTA12, PTA4, PTA5);
// Call to the conversion function
float H, S, I;
float rgbw[4];
led.hsi2rgbw(H, S, I, rgbw);

// Assign 4 PWM outputs, no converted RGBW value will be returned.
hsi2rgbw_pwm led(PTD4, PTA12, PTA4, PTA5);
// Call to the conversion function
float H, S, I;
led.hsi2rgbw(H, S, I);

// No PWM outputs are assigned, only the converted RGBW value will be returned.
hsi2rgbw_pwm led(NC);
// Call to the conversion function
float H, S, I;
float rgbw[4];
led.hsi2rgbw(H, S, I, rgbw);

// Assign 3 PWM outputs and also allow to return the converted RGB value.
hsi2rgbw_pwm led(PTD4, PTA12, PTA4);
// Call to the conversion function
float H, S, I;
float rgb[4];
led.hsi2rgbw(H, S, I, rgbw);

// Assign 3 PWM outputs, no converted RGB value will be returned.
hsi2rgbw_pwm led(PTD4, PTA12, PTA4);
// Call to the conversion function
float H, S, I;
led.hsi2rgbw(H, S, I);

// No PWM outputs are assigned, only the converted RGB value will be returned.
hsi2rgbw_pwm led(NC);
//Set RGB mode
led.colorMode(RGB);
// Call to the conversion function
float H, S, I;
float rgb[4];
led.hsi2rgbw(H, S, I, rgb);


Hue, Saturation and Intesity range

Hue0...360Color value : 0 or 360 = red, 120 = green, 240 = blue, 60 = yellow, 300 = magenta, ...
Saturation0...1Color saturation : 0 = white, 1 = max. color, 0.7 is a nice pastel
Intensity0...1Color intensity : 0 = off, 1 = full intensity


RGB(W) return value
In your main program, declare an array of 4 float values and pass the pointer to this array to the function:
Even when RGB is used, do declare an array of 4 float values.

float rgbw[4];

Upon exit, the function will return the values in rgbw[], representing the red, green, blue and white component that corresponds to the chosen HSI values. Range : 0...1 where 0 = minimum and 1 = maximum.

rgbw[0]RED
rgbw[1]GREEN
rgbw[2]BLUE
rgbw[3]WHITENote : this value has no significance when RGB mode is used.


parabolic mode
By default, each component of the RGB(W) return value is mapped to a parabolic scale.
Call led.parabolic(0); to disable the parabolic mapping.

Choose RGB or RGBW mode
By default, the conversion return value is set to RGBW.
Call led.colorMode(RGB); to change to RGB mode.
Call led.colorMode(RGBW); to go back to RGBW mode.

Change PWM period
By default, the PWM period is set to 4ms (250Hz).
Call led.period(n); to change the PWM period (replace n with a value in ms).

Change PWM values
We can alter the PWM outputs directly, without passing through the HSI to RGB(W) conversion.
declare float rgbw[4]; and set each value of this array.
Call led.pwm(rgbw); to write the values directly to the PWM outputs.

Invert PWM channels
When common anode LEDs are used, we need to invert the PWM signal..
Call led.invertpwm(1); to use the inverted PWM signal.

NOTES
period() and pwm() will not change any value if no PWM pins are declared.
Do not forget to replace led. with the declared function name.

Committer:
frankvnk
Date:
Tue Feb 18 19:14:48 2014 +0000
Revision:
5:68daa710ba3f
Parent:
4:a16b9c09561e
Doxygen Hue range updated

Who changed what in which revision?

UserRevisionLine numberNew contents of line
frankvnk 2:d164d60999c4 1 /**************************************************************************************************************
frankvnk 2:d164d60999c4 2 ***** *****
frankvnk 2:d164d60999c4 3 ***** Name: hsi2rgbw.cpp *****
frankvnk 2:d164d60999c4 4 ***** Date: 22/12/2013 *****
frankvnk 2:d164d60999c4 5 ***** Auth: Frank Vannieuwkerke *****
frankvnk 2:d164d60999c4 6 ***** Func: library for converting HSI color space values to RGBW *****
frankvnk 2:d164d60999c4 7 ***** *****
frankvnk 2:d164d60999c4 8 ***** Code ported from http://saikoled.com - Copyright 2013, Brian Neltner *****
frankvnk 2:d164d60999c4 9 ***** http://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white *****
frankvnk 2:d164d60999c4 10 ***** http://blog.saikoled.com/post/43693602826/why-every-led-light-should-be-using-hsi-colorspace *****
frankvnk 2:d164d60999c4 11 ***** https://github.com/saikoLED/MyKi/blob/master/myki_16_bit_random_fade/myki_16_bit_random_fade.ino *****
frankvnk 2:d164d60999c4 12 ***** https://github.com/saikoLED/MyKi/blob/master/myki_16_bit_fade/myki_16_bit_fade.ino *****
frankvnk 2:d164d60999c4 13 ***** *****
frankvnk 2:d164d60999c4 14 ***** This program is free software: you can redistribute it and/or modify *****
frankvnk 2:d164d60999c4 15 ***** it under the terms of the GNU General Public License as published by *****
frankvnk 2:d164d60999c4 16 ***** the Free Software Foundation, version 3 of the License. *****
frankvnk 2:d164d60999c4 17 ***** *****
frankvnk 2:d164d60999c4 18 ***** This program is distributed in the hope that it will be useful, *****
frankvnk 2:d164d60999c4 19 ***** but WITHOUT ANY WARRANTY; without even the implied warranty of *****
frankvnk 2:d164d60999c4 20 ***** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *****
frankvnk 2:d164d60999c4 21 ***** GNU General Public License for more details. *****
frankvnk 2:d164d60999c4 22 ***** *****
frankvnk 2:d164d60999c4 23 ***** A copy of the GNU General Public License can be found at *****
frankvnk 2:d164d60999c4 24 ***** http://www.gnu.org/licenses/gpl.html *****
frankvnk 2:d164d60999c4 25 **************************************************************************************************************/
frankvnk 2:d164d60999c4 26
frankvnk 0:dd0e7a8a4572 27 #include "hsi2rgbw_pwm.h"
frankvnk 0:dd0e7a8a4572 28
frankvnk 0:dd0e7a8a4572 29 #define PI 3.14159265
frankvnk 0:dd0e7a8a4572 30
frankvnk 4:a16b9c09561e 31 PwmOut *_red;
frankvnk 4:a16b9c09561e 32 PwmOut *_green;
frankvnk 4:a16b9c09561e 33 PwmOut *_blue;
frankvnk 4:a16b9c09561e 34 PwmOut *_white;
frankvnk 4:a16b9c09561e 35
frankvnk 4:a16b9c09561e 36 hsi2rgbw_pwm::hsi2rgbw_pwm(PinName pred, PinName pgreen, PinName pblue, PinName pwhite)
frankvnk 0:dd0e7a8a4572 37 {
frankvnk 0:dd0e7a8a4572 38 parabol = 1;
frankvnk 4:a16b9c09561e 39 use_invpwm = 0;
frankvnk 0:dd0e7a8a4572 40 use_rgbw = RGBW;
frankvnk 4:a16b9c09561e 41 if(pred != NC) _red = new PwmOut (pred);
frankvnk 4:a16b9c09561e 42 if(pgreen != NC) _green = new PwmOut (pgreen);
frankvnk 4:a16b9c09561e 43 if(pblue != NC) _blue = new PwmOut (pblue);
frankvnk 4:a16b9c09561e 44 if(pwhite != NC) _white = new PwmOut (pwhite);
frankvnk 4:a16b9c09561e 45 if((pred != NC) && (pgreen != NC) && (pblue != NC) && (pwhite == NC))
frankvnk 4:a16b9c09561e 46 use_rgbw = RGB;
frankvnk 4:a16b9c09561e 47 if((pred == NC) && (pgreen == NC) && (pblue == NC) && (pwhite == NC))
frankvnk 0:dd0e7a8a4572 48 {
frankvnk 0:dd0e7a8a4572 49 use_pwm = 0;
frankvnk 0:dd0e7a8a4572 50 }
frankvnk 0:dd0e7a8a4572 51 else
frankvnk 0:dd0e7a8a4572 52 {
frankvnk 0:dd0e7a8a4572 53 use_pwm = 1;
frankvnk 0:dd0e7a8a4572 54 //Setup PWM channels - default period = 4 ms (250Hz)
frankvnk 4:a16b9c09561e 55 _red->period_ms(4);
frankvnk 4:a16b9c09561e 56 _green->period_ms(4);
frankvnk 4:a16b9c09561e 57 _blue->period_ms(4);
frankvnk 4:a16b9c09561e 58 if(pwhite != NC)
frankvnk 4:a16b9c09561e 59 _white->period_ms(4);
frankvnk 0:dd0e7a8a4572 60
frankvnk 0:dd0e7a8a4572 61 // Initial RGB values.
frankvnk 4:a16b9c09561e 62 _red->write(0.0f);
frankvnk 4:a16b9c09561e 63 _green->write(0.0f);
frankvnk 4:a16b9c09561e 64 _blue->write(0.0f);
frankvnk 4:a16b9c09561e 65 if(pwhite != NC)
frankvnk 4:a16b9c09561e 66 _white->write(0.0f);
frankvnk 0:dd0e7a8a4572 67 }
frankvnk 0:dd0e7a8a4572 68 }
frankvnk 0:dd0e7a8a4572 69
frankvnk 0:dd0e7a8a4572 70 void hsi2rgbw_pwm::hsi2rgbw(float H, float S, float I, float* rgbw) {
frankvnk 0:dd0e7a8a4572 71 float _rgbw[4];
frankvnk 0:dd0e7a8a4572 72 if(rgbw == NULL)
frankvnk 0:dd0e7a8a4572 73 rgbw = _rgbw;
frankvnk 0:dd0e7a8a4572 74 float cos_h, Srgb;
frankvnk 0:dd0e7a8a4572 75 H = fmod(H,360); // cycle H around to 0-360 degrees
frankvnk 0:dd0e7a8a4572 76 H = PI*H/(float)180; // Convert to radians.
frankvnk 0:dd0e7a8a4572 77 S = S>0?(S<1?S:1):0; // clamp S and I to interval [0,1]
frankvnk 0:dd0e7a8a4572 78 I = I>0?(I<1?I:1):0;
frankvnk 4:a16b9c09561e 79 if(use_rgbw == RGBW)
frankvnk 0:dd0e7a8a4572 80 Srgb = 1;
frankvnk 0:dd0e7a8a4572 81 else
frankvnk 0:dd0e7a8a4572 82 {
frankvnk 0:dd0e7a8a4572 83 Srgb = S;
frankvnk 0:dd0e7a8a4572 84 S = 1;
frankvnk 0:dd0e7a8a4572 85 }
frankvnk 0:dd0e7a8a4572 86 // This section is modified by the addition of white so that it assumes
frankvnk 0:dd0e7a8a4572 87 // fully saturated colors, and then scales with white to lower saturation.
frankvnk 0:dd0e7a8a4572 88 //
frankvnk 0:dd0e7a8a4572 89 // Next, scale appropriately the pure color by mixing with the white channel.
frankvnk 0:dd0e7a8a4572 90 // Saturation is defined as "the ratio of colorfulness to brightness" so we will
frankvnk 0:dd0e7a8a4572 91 // do this by a simple ratio wherein the color values are scaled down by (1-S)
frankvnk 0:dd0e7a8a4572 92 // while the white LED is placed at S.
frankvnk 0:dd0e7a8a4572 93
frankvnk 0:dd0e7a8a4572 94 // This will maintain constant brightness because in HSI, R+B+G = I. Thus,
frankvnk 0:dd0e7a8a4572 95 // S*(R+B+G) = S*I. If we add to this (1-S)*I, where I is the total intensity,
frankvnk 0:dd0e7a8a4572 96 // the sum intensity stays constant while the ratio of colorfulness to brightness
frankvnk 0:dd0e7a8a4572 97 // goes down by S linearly relative to total Intensity, which is constant.
frankvnk 0:dd0e7a8a4572 98
frankvnk 0:dd0e7a8a4572 99 if(H < 2.09439) {
frankvnk 0:dd0e7a8a4572 100 cos_h = cos(H) / cos(1.047196667-H);
frankvnk 0:dd0e7a8a4572 101 rgbw[0] = S*I/3*(1+Srgb*cos_h);
frankvnk 0:dd0e7a8a4572 102 rgbw[1] = S*I/3*(1+Srgb*(1-cos_h));
frankvnk 4:a16b9c09561e 103 if(use_rgbw == RGBW)
frankvnk 0:dd0e7a8a4572 104 {
frankvnk 0:dd0e7a8a4572 105 rgbw[2] = 0;
frankvnk 0:dd0e7a8a4572 106 rgbw[3] = (1-S)*I;
frankvnk 0:dd0e7a8a4572 107 }
frankvnk 0:dd0e7a8a4572 108 else
frankvnk 0:dd0e7a8a4572 109 rgbw[2] = I/3*(1-Srgb);
frankvnk 0:dd0e7a8a4572 110 } else if(H < 4.188787) {
frankvnk 0:dd0e7a8a4572 111 H = H - 2.09439;
frankvnk 0:dd0e7a8a4572 112 cos_h = cos(H) / cos(1.047196667-H);
frankvnk 0:dd0e7a8a4572 113 rgbw[1] = S*I/3*(1+Srgb*cos_h);
frankvnk 0:dd0e7a8a4572 114 rgbw[2] = S*I/3*(1+Srgb*(1-cos_h));
frankvnk 4:a16b9c09561e 115 if(use_rgbw == RGBW)
frankvnk 0:dd0e7a8a4572 116 {
frankvnk 0:dd0e7a8a4572 117 rgbw[0] = 0;
frankvnk 0:dd0e7a8a4572 118 rgbw[3] = (1-S)*I;
frankvnk 0:dd0e7a8a4572 119 }
frankvnk 0:dd0e7a8a4572 120 else
frankvnk 0:dd0e7a8a4572 121 rgbw[0] = I/3*(1-Srgb);
frankvnk 0:dd0e7a8a4572 122 } else {
frankvnk 0:dd0e7a8a4572 123 H = H - 4.188787;
frankvnk 0:dd0e7a8a4572 124 cos_h = cos(H) / cos(1.047196667-H);
frankvnk 0:dd0e7a8a4572 125 rgbw[2] = S*I/3*(1+Srgb*cos_h);
frankvnk 0:dd0e7a8a4572 126 rgbw[0] = S*I/3*(1+Srgb*(1-cos_h));
frankvnk 4:a16b9c09561e 127 if(use_rgbw == RGBW)
frankvnk 0:dd0e7a8a4572 128 {
frankvnk 0:dd0e7a8a4572 129 rgbw[1] = 0;
frankvnk 0:dd0e7a8a4572 130 rgbw[3] = (1-S)*I;
frankvnk 0:dd0e7a8a4572 131 }
frankvnk 0:dd0e7a8a4572 132 else
frankvnk 0:dd0e7a8a4572 133 rgbw[1] = I/3*(1-Srgb);
frankvnk 0:dd0e7a8a4572 134 }
frankvnk 4:a16b9c09561e 135
frankvnk 4:a16b9c09561e 136 if(use_invpwm)
frankvnk 4:a16b9c09561e 137 {
frankvnk 4:a16b9c09561e 138 rgbw[0] = (1.0f - rgbw[0]);
frankvnk 4:a16b9c09561e 139 rgbw[1] = (1.0f - rgbw[1]);
frankvnk 4:a16b9c09561e 140 rgbw[2] = (1.0f - rgbw[2]);
frankvnk 4:a16b9c09561e 141 if(use_rgbw == RGBW)
frankvnk 4:a16b9c09561e 142 rgbw[3] = (1.0f - rgbw[3]);
frankvnk 4:a16b9c09561e 143 }
frankvnk 4:a16b9c09561e 144
frankvnk 0:dd0e7a8a4572 145 // parabolic mapping.
frankvnk 0:dd0e7a8a4572 146 if(parabol) {
frankvnk 0:dd0e7a8a4572 147 rgbw[0] *= rgbw[0]; // RED
frankvnk 0:dd0e7a8a4572 148 rgbw[1] *= rgbw[1]; // GREEN
frankvnk 0:dd0e7a8a4572 149 rgbw[2] *= rgbw[2]; // BLUE
frankvnk 4:a16b9c09561e 150 if(use_rgbw == RGBW)
frankvnk 0:dd0e7a8a4572 151 rgbw[3] *= rgbw[3]; // WHITE
frankvnk 0:dd0e7a8a4572 152 }
frankvnk 4:a16b9c09561e 153
frankvnk 0:dd0e7a8a4572 154 if(use_pwm)
frankvnk 0:dd0e7a8a4572 155 {
frankvnk 4:a16b9c09561e 156 _red->write(rgbw[0]);
frankvnk 4:a16b9c09561e 157 _green->write(rgbw[1]);
frankvnk 4:a16b9c09561e 158 _blue->write(rgbw[2]);
frankvnk 4:a16b9c09561e 159 if(use_rgbw == RGBW)
frankvnk 4:a16b9c09561e 160 _white->write(rgbw[3]);
frankvnk 0:dd0e7a8a4572 161 }
frankvnk 0:dd0e7a8a4572 162 }
frankvnk 0:dd0e7a8a4572 163
frankvnk 0:dd0e7a8a4572 164 void hsi2rgbw_pwm::period(uint32_t per)
frankvnk 0:dd0e7a8a4572 165 {
frankvnk 0:dd0e7a8a4572 166 if(use_pwm)
frankvnk 0:dd0e7a8a4572 167 {
frankvnk 4:a16b9c09561e 168 _red->period_us(per);
frankvnk 4:a16b9c09561e 169 _green->period_us(per);
frankvnk 4:a16b9c09561e 170 _blue->period_us(per);
frankvnk 4:a16b9c09561e 171 if(use_rgbw == RGBW)
frankvnk 4:a16b9c09561e 172 _white->period_us(per);
frankvnk 0:dd0e7a8a4572 173 }
frankvnk 0:dd0e7a8a4572 174 }
frankvnk 0:dd0e7a8a4572 175
frankvnk 0:dd0e7a8a4572 176 void hsi2rgbw_pwm::pwm(float* rgbw)
frankvnk 0:dd0e7a8a4572 177 {
frankvnk 0:dd0e7a8a4572 178 if(use_pwm)
frankvnk 0:dd0e7a8a4572 179 {
frankvnk 4:a16b9c09561e 180 if(!use_invpwm)
frankvnk 4:a16b9c09561e 181 {
frankvnk 4:a16b9c09561e 182 _red->write(rgbw[0]);
frankvnk 4:a16b9c09561e 183 _green->write(rgbw[1]);
frankvnk 4:a16b9c09561e 184 _blue->write(rgbw[2]);
frankvnk 4:a16b9c09561e 185 if(use_rgbw == RGBW)
frankvnk 4:a16b9c09561e 186 _white->write(rgbw[3]);
frankvnk 4:a16b9c09561e 187 }
frankvnk 4:a16b9c09561e 188 else
frankvnk 4:a16b9c09561e 189 {
frankvnk 4:a16b9c09561e 190 _red->write(1.0f - rgbw[0]);
frankvnk 4:a16b9c09561e 191 _green->write(1.0f - rgbw[1]);
frankvnk 4:a16b9c09561e 192 _blue->write(1.0f - rgbw[2]);
frankvnk 4:a16b9c09561e 193 if(use_rgbw == RGBW)
frankvnk 4:a16b9c09561e 194 _white->write(1.0f - rgbw[3]);
frankvnk 4:a16b9c09561e 195 }
frankvnk 0:dd0e7a8a4572 196 }
frankvnk 0:dd0e7a8a4572 197 }
frankvnk 0:dd0e7a8a4572 198
frankvnk 0:dd0e7a8a4572 199 void hsi2rgbw_pwm::parabolic(bool para)
frankvnk 0:dd0e7a8a4572 200 {
frankvnk 0:dd0e7a8a4572 201 parabol = para;
frankvnk 0:dd0e7a8a4572 202 }
frankvnk 0:dd0e7a8a4572 203
frankvnk 0:dd0e7a8a4572 204 void hsi2rgbw_pwm::colorMode(bool como)
frankvnk 0:dd0e7a8a4572 205 {
frankvnk 0:dd0e7a8a4572 206 use_rgbw = como;
frankvnk 0:dd0e7a8a4572 207 }
frankvnk 3:dda6914d713f 208
frankvnk 4:a16b9c09561e 209 void hsi2rgbw_pwm::invertpwm(bool invpwm)
frankvnk 4:a16b9c09561e 210 {
frankvnk 4:a16b9c09561e 211 use_invpwm = invpwm;
frankvnk 4:a16b9c09561e 212 }