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:
Mon Dec 30 11:22:53 2013 +0000
Revision:
3:dda6914d713f
Parent:
2:d164d60999c4
Child:
4:a16b9c09561e
doc error corrected.
; changed period_ms to period_us

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 0:dd0e7a8a4572 31 hsi2rgbw_pwm::hsi2rgbw_pwm(PinName red, PinName green, PinName blue, PinName white) : _red(red), _green(green), _blue(blue), _white(white)
frankvnk 0:dd0e7a8a4572 32 {
frankvnk 0:dd0e7a8a4572 33 parabol = 1;
frankvnk 0:dd0e7a8a4572 34 use_rgbw = RGBW;
frankvnk 0:dd0e7a8a4572 35 if(_red == NC && _green == NC && _blue == NC)
frankvnk 0:dd0e7a8a4572 36 {
frankvnk 0:dd0e7a8a4572 37 use_pwm = 0;
frankvnk 0:dd0e7a8a4572 38 }
frankvnk 0:dd0e7a8a4572 39 else
frankvnk 0:dd0e7a8a4572 40 {
frankvnk 0:dd0e7a8a4572 41 use_pwm = 1;
frankvnk 0:dd0e7a8a4572 42 //Setup PWM channels - default period = 4 ms (250Hz)
frankvnk 0:dd0e7a8a4572 43 _red.period_ms(4);
frankvnk 0:dd0e7a8a4572 44 _green.period_ms(4);
frankvnk 0:dd0e7a8a4572 45 _blue.period_ms(4);
frankvnk 0:dd0e7a8a4572 46 if(_white != NC)
frankvnk 0:dd0e7a8a4572 47 _white.period_ms(4);
frankvnk 0:dd0e7a8a4572 48
frankvnk 0:dd0e7a8a4572 49 // Initial RGB values.
frankvnk 0:dd0e7a8a4572 50 _red = 0.0f;
frankvnk 0:dd0e7a8a4572 51 _green = 0.0f;
frankvnk 0:dd0e7a8a4572 52 _blue = 0.0f;
frankvnk 0:dd0e7a8a4572 53 if(_white != NC)
frankvnk 0:dd0e7a8a4572 54 _white = 0.0f;
frankvnk 0:dd0e7a8a4572 55 }
frankvnk 0:dd0e7a8a4572 56 }
frankvnk 0:dd0e7a8a4572 57
frankvnk 0:dd0e7a8a4572 58 void hsi2rgbw_pwm::hsi2rgbw(float H, float S, float I, float* rgbw) {
frankvnk 0:dd0e7a8a4572 59 float _rgbw[4];
frankvnk 0:dd0e7a8a4572 60 if(rgbw == NULL)
frankvnk 0:dd0e7a8a4572 61 rgbw = _rgbw;
frankvnk 0:dd0e7a8a4572 62 float cos_h, Srgb;
frankvnk 0:dd0e7a8a4572 63 H = fmod(H,360); // cycle H around to 0-360 degrees
frankvnk 0:dd0e7a8a4572 64 H = PI*H/(float)180; // Convert to radians.
frankvnk 0:dd0e7a8a4572 65 S = S>0?(S<1?S:1):0; // clamp S and I to interval [0,1]
frankvnk 0:dd0e7a8a4572 66 I = I>0?(I<1?I:1):0;
frankvnk 0:dd0e7a8a4572 67 if(use_rgbw)
frankvnk 0:dd0e7a8a4572 68 Srgb = 1;
frankvnk 0:dd0e7a8a4572 69 else
frankvnk 0:dd0e7a8a4572 70 {
frankvnk 0:dd0e7a8a4572 71 Srgb = S;
frankvnk 0:dd0e7a8a4572 72 S = 1;
frankvnk 0:dd0e7a8a4572 73 }
frankvnk 0:dd0e7a8a4572 74 // This section is modified by the addition of white so that it assumes
frankvnk 0:dd0e7a8a4572 75 // fully saturated colors, and then scales with white to lower saturation.
frankvnk 0:dd0e7a8a4572 76 //
frankvnk 0:dd0e7a8a4572 77 // Next, scale appropriately the pure color by mixing with the white channel.
frankvnk 0:dd0e7a8a4572 78 // Saturation is defined as "the ratio of colorfulness to brightness" so we will
frankvnk 0:dd0e7a8a4572 79 // do this by a simple ratio wherein the color values are scaled down by (1-S)
frankvnk 0:dd0e7a8a4572 80 // while the white LED is placed at S.
frankvnk 0:dd0e7a8a4572 81
frankvnk 0:dd0e7a8a4572 82 // This will maintain constant brightness because in HSI, R+B+G = I. Thus,
frankvnk 0:dd0e7a8a4572 83 // S*(R+B+G) = S*I. If we add to this (1-S)*I, where I is the total intensity,
frankvnk 0:dd0e7a8a4572 84 // the sum intensity stays constant while the ratio of colorfulness to brightness
frankvnk 0:dd0e7a8a4572 85 // goes down by S linearly relative to total Intensity, which is constant.
frankvnk 0:dd0e7a8a4572 86
frankvnk 0:dd0e7a8a4572 87 if(H < 2.09439) {
frankvnk 0:dd0e7a8a4572 88 cos_h = cos(H) / cos(1.047196667-H);
frankvnk 0:dd0e7a8a4572 89 rgbw[0] = S*I/3*(1+Srgb*cos_h);
frankvnk 0:dd0e7a8a4572 90 rgbw[1] = S*I/3*(1+Srgb*(1-cos_h));
frankvnk 0:dd0e7a8a4572 91 if(use_rgbw)
frankvnk 0:dd0e7a8a4572 92 {
frankvnk 0:dd0e7a8a4572 93 rgbw[2] = 0;
frankvnk 0:dd0e7a8a4572 94 rgbw[3] = (1-S)*I;
frankvnk 0:dd0e7a8a4572 95 }
frankvnk 0:dd0e7a8a4572 96 else
frankvnk 0:dd0e7a8a4572 97 rgbw[2] = I/3*(1-Srgb);
frankvnk 0:dd0e7a8a4572 98 } else if(H < 4.188787) {
frankvnk 0:dd0e7a8a4572 99 H = H - 2.09439;
frankvnk 0:dd0e7a8a4572 100 cos_h = cos(H) / cos(1.047196667-H);
frankvnk 0:dd0e7a8a4572 101 rgbw[1] = S*I/3*(1+Srgb*cos_h);
frankvnk 0:dd0e7a8a4572 102 rgbw[2] = S*I/3*(1+Srgb*(1-cos_h));
frankvnk 0:dd0e7a8a4572 103 if(use_rgbw)
frankvnk 0:dd0e7a8a4572 104 {
frankvnk 0:dd0e7a8a4572 105 rgbw[0] = 0;
frankvnk 0:dd0e7a8a4572 106 rgbw[3] = (1-S)*I;
frankvnk 0:dd0e7a8a4572 107 }
frankvnk 0:dd0e7a8a4572 108 else
frankvnk 0:dd0e7a8a4572 109 rgbw[0] = I/3*(1-Srgb);
frankvnk 0:dd0e7a8a4572 110 } else {
frankvnk 0:dd0e7a8a4572 111 H = H - 4.188787;
frankvnk 0:dd0e7a8a4572 112 cos_h = cos(H) / cos(1.047196667-H);
frankvnk 0:dd0e7a8a4572 113 rgbw[2] = S*I/3*(1+Srgb*cos_h);
frankvnk 0:dd0e7a8a4572 114 rgbw[0] = S*I/3*(1+Srgb*(1-cos_h));
frankvnk 0:dd0e7a8a4572 115 if(use_rgbw)
frankvnk 0:dd0e7a8a4572 116 {
frankvnk 0:dd0e7a8a4572 117 rgbw[1] = 0;
frankvnk 0:dd0e7a8a4572 118 rgbw[3] = (1-S)*I;
frankvnk 0:dd0e7a8a4572 119 }
frankvnk 0:dd0e7a8a4572 120 else
frankvnk 0:dd0e7a8a4572 121 rgbw[1] = I/3*(1-Srgb);
frankvnk 0:dd0e7a8a4572 122 }
frankvnk 0:dd0e7a8a4572 123
frankvnk 0:dd0e7a8a4572 124 // parabolic mapping.
frankvnk 0:dd0e7a8a4572 125 if(parabol) {
frankvnk 0:dd0e7a8a4572 126 rgbw[0] *= rgbw[0]; // RED
frankvnk 0:dd0e7a8a4572 127 rgbw[1] *= rgbw[1]; // GREEN
frankvnk 0:dd0e7a8a4572 128 rgbw[2] *= rgbw[2]; // BLUE
frankvnk 0:dd0e7a8a4572 129 if(use_rgbw)
frankvnk 0:dd0e7a8a4572 130 rgbw[3] *= rgbw[3]; // WHITE
frankvnk 0:dd0e7a8a4572 131 }
frankvnk 0:dd0e7a8a4572 132 if(use_pwm)
frankvnk 0:dd0e7a8a4572 133 {
frankvnk 0:dd0e7a8a4572 134 _red = rgbw[0];
frankvnk 0:dd0e7a8a4572 135 _green = rgbw[1];
frankvnk 0:dd0e7a8a4572 136 _blue = rgbw[2];
frankvnk 0:dd0e7a8a4572 137 if(_white != NC)
frankvnk 0:dd0e7a8a4572 138 _white = rgbw[3];
frankvnk 0:dd0e7a8a4572 139 }
frankvnk 0:dd0e7a8a4572 140 }
frankvnk 0:dd0e7a8a4572 141
frankvnk 0:dd0e7a8a4572 142 void hsi2rgbw_pwm::period(uint32_t per)
frankvnk 0:dd0e7a8a4572 143 {
frankvnk 0:dd0e7a8a4572 144 if(use_pwm)
frankvnk 0:dd0e7a8a4572 145 {
frankvnk 3:dda6914d713f 146 _red.period_us(per);
frankvnk 3:dda6914d713f 147 _green.period_us(per);
frankvnk 3:dda6914d713f 148 _blue.period_us(per);
frankvnk 0:dd0e7a8a4572 149 if(_white != NC)
frankvnk 3:dda6914d713f 150 _white.period_us(per);
frankvnk 0:dd0e7a8a4572 151 }
frankvnk 0:dd0e7a8a4572 152 }
frankvnk 0:dd0e7a8a4572 153
frankvnk 0:dd0e7a8a4572 154 void hsi2rgbw_pwm::pwm(float* rgbw)
frankvnk 0:dd0e7a8a4572 155 {
frankvnk 0:dd0e7a8a4572 156 if(use_pwm)
frankvnk 0:dd0e7a8a4572 157 {
frankvnk 0:dd0e7a8a4572 158 _red = rgbw[0];
frankvnk 0:dd0e7a8a4572 159 _green = rgbw[1];
frankvnk 0:dd0e7a8a4572 160 _blue = rgbw[2];
frankvnk 0:dd0e7a8a4572 161 if(_white != NC)
frankvnk 0:dd0e7a8a4572 162 _white = rgbw[3];
frankvnk 0:dd0e7a8a4572 163 }
frankvnk 0:dd0e7a8a4572 164 }
frankvnk 0:dd0e7a8a4572 165
frankvnk 0:dd0e7a8a4572 166 void hsi2rgbw_pwm::parabolic(bool para)
frankvnk 0:dd0e7a8a4572 167 {
frankvnk 0:dd0e7a8a4572 168 parabol = para;
frankvnk 0:dd0e7a8a4572 169 }
frankvnk 0:dd0e7a8a4572 170
frankvnk 0:dd0e7a8a4572 171 void hsi2rgbw_pwm::colorMode(bool como)
frankvnk 0:dd0e7a8a4572 172 {
frankvnk 0:dd0e7a8a4572 173 use_rgbw = como;
frankvnk 0:dd0e7a8a4572 174 }
frankvnk 3:dda6914d713f 175