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 23 20:24:17 2013 +0000
Revision:
0:dd0e7a8a4572
Child:
2:d164d60999c4
Initial release

Who changed what in which revision?

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