Last active
August 27, 2016 16:23
-
-
Save ChrisHinde/b15ca6199c7dec46e8b26f0a60af399c to your computer and use it in GitHub Desktop.
Functions to fade between colors with Lua on NodeMCU
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--[[ color_fade.lua | |
Created by Christopher Hindefjord (ChrisHinde / chris@hindefjord.se) | |
CC-0 > No copyright claimed - You are free to use these snippets | |
These are a couple of functions used to fade between colors with an RGB LED (or strip). | |
This is made for the NodeMCU with LUA interpreter (Tested with Lua5.1.4 on SDK 1.5.4.1). | |
The only requirments are the PWM and TMR (timer) modules | |
NOTE: See (and include) rgb_pwm.lua (https://gist.github.com/ChrisHinde/0b0e6eaf2efa42faa4d6757d973caf13) | |
for needed support functions (and variables) | |
The code uses the timer module to run the fading, this makes it "unblocking", so the MCU can do other things while fading | |
Tip: Remove the comments from the code before loading it to your NodeMCU (that will save you some memory) | |
Note: These letters will be used to represent colors etc in the comments: | |
R - Red | |
Y - Yellow | |
G - Green | |
C - Cyan | |
B - Blue | |
M - Magenta | |
H - Hue | |
S - Saturation | |
V - Value/Brightness | |
It includes two different methods: | |
* Hue/HSV | |
Uses a HSV value, and more specifically the Hue component to fade colors. | |
Pros: | |
+ It's simple to go between colors/hues, just increase (or decrease) the Hue component | |
(Hue goes: Red, Yellow, Green, Cyan, Blue, Magenta, Red in 0-255) | |
+ You can easily change saturation ("more or less white") and brightness by changing those componentz | |
(S and V or hsv[2] and hsv[3]) without "touching" the color | |
(Potentially, you could fade the intensity seperate) | |
Cons: | |
- It requires a convertion from HSV to RGB and so an extra function to do that task. | |
That convertion/function could be considered complex or at least conveluted/non-trivial | |
- You're restricted to 256 steps, which may make the fading look less smooth (and it will loop around faster) | |
(You could potentially increase the number of steps, but still the RGB fade has 1024*6 = 6144 individual steps) | |
- Requires a bit more memory | |
* RGB | |
Step through (up and down) the RGB components (from 0-1023 if you have a 10-bit PWM) to fade colors | |
Pros: | |
+ Can be done without any extra support functions, just simple arithmetics (adding and subtracting) | |
+ Requieres less memory | |
+ A lot more steps (1024*6 = 6144, 2 cycles [up and down] per channel) which means smoother (and slower) fading | |
Cons: | |
- The fading function itself get a bit more complex since it needs to traverse the RGB components to fade between them | |
- If you want to change saturation and/or brightness, you need to recalculate each channel accordingly | |
and do this without changing "the current RGB status" (which are used for fading, so between the fading function and the PWM output) | |
(to change the brightness, you just multiply each channel with say 0.5 [50%], to change saturation you need to multiply each channel | |
with 0.5 and then add 512 [50% of max]) | |
(This is not included in this code!) | |
- Slower fading/animation due to more steps. (This could be handled by increasing the step size to 2 [=double the speed] or more) | |
(During my tests on my NodeMCU I noticed that, with the current setup [i.e. a timer = non-blocking], the unit would crash | |
if I set the timing really low [to increase the fading speed], so be careful and don't go bellow 10ms [50ms+ is recommended]) | |
--]] | |
-- Function to map a value from one range to another | |
-- This is used in the HSV to RGB, since the HSV output is 8 bit (0-255) and the PWM is 10 bit (0-1023) | |
-- It uses 0 as a set minimum for both ranges | |
-- | |
-- Return: The value mapped to the new range (not rounded) | |
-- | |
-- value: The value to be mapped | |
-- max_in: The maximum value of the current range (i.e. 255) | |
-- max_out: The maximum value of the wanted range (i.e. 1023) | |
function map(value, max_in, max_out) | |
return (value / max_in) * max_out; | |
end | |
-- This function converts HSV to RGB | |
-- This code is borrowed from http://stackoverflow.com/a/22120275/4204058 and then converted to Lua | |
-- The method to do this is straightforward and well documented, you can read more about it here: | |
-- http://www.rapidtables.com/convert/color/hsv-to-rgb.htm | |
-- https://en.wikipedia.org/wiki/HSL_and_HSV#Converting_to_RGB | |
-- | |
-- Return: An array with three values representing {R,G,B} | |
-- | |
-- hsv: An array with three values for {H,S,V} | |
-- max: The maximum value for the RGB (default: 255) | |
function hsv_to_rgb( hsv, max ) | |
-- If the third component of the HSV (value) is 0, just return black.. | |
if hsv[3] == 0 then | |
return {0,0,0} | |
end | |
-- The default max value is 255 | |
max = max or 255 | |
-- Initiate variables | |
region = 0; p = 0; q = 0; t = 0 | |
h = hsv[1]; s = hsv[2]; v = hsv[3]; | |
remainder = 0 | |
-- Each region is 60 degrees of the hue wheel (6 regions) | |
-- With 0-255, each region is 43 (or 42.5 [255/6], but that introduces some errors) | |
region = math.floor(h / 43) | |
remainder = (h - (region * 43)) * 6 | |
-- Do some HSV calculations | |
-- In the original version this uses bitwise operators to shift it 8 bits to the right | |
-- but since Lua doesn't have bitwise operators (and the library just adds extra fat, there's no reason to use it here) | |
-- we divide by 256 (and round it down) instead | |
p = math.floor( (v * (255 - s)) / 256 ) | |
q = math.floor( (v * (255 - (s * remainder) / 256 )) / 256 ) | |
t = math.floor( (v * (255 - (s * (255 - remainder)) / 256 )) / 256 ) | |
-- Map the values if max is more than the default 255 | |
if max > 255 then | |
p = map(p,255,max) | |
q = map(q,255,max) | |
v = map(v,255,max) | |
t = map(t,255,max) | |
end | |
-- Map calculated values to RGB depending on region | |
if region == 0 then | |
return {v,t,p} | |
elseif region == 1 then | |
return {q,v,p} | |
elseif region == 2 then | |
return {p,v,t} | |
elseif region == 3 then | |
return {p,q,v} | |
elseif region == 4 then | |
return {t,p,v} | |
else | |
return {v,p,q} | |
end | |
end | |
-- The amount we should go in each step when fading, bigger = faster | |
PWM_STEP = 1 | |
-- The "alam" we want to use for fading | |
FADE_ALARM = 2 | |
-- Set global variable for HSV (this is sort of unnecessary in Lua, but good form) | |
curr_hsv = {0,255,255} -- Full saturation and value at hue 0 would be full red | |
-- Do the actually hue fading | |
-- This just fades one step | |
-- It's in its own function to make it easier (and cleaner) to call with tmr.alarm | |
-- | |
-- Note: Since hue goes from "red to red" (both 0 and 255 is red), it's easy to just loop around | |
-- and it just goes round and round the wheel ( R > Y > G > C > B > M > R ...) | |
-- If you wanted to, you could make it go backwards or "turn" when it reaches the end/beginning | |
-- and it would create "palindrome effect": | |
-- ( R > Y > G > C > B > M > R > M > B > C > G > Y > R > Y > G > C > B > M > R ...) | |
function hue_fade_do() | |
-- Increase the HSV with one step (could be negative) | |
curr_hsv[1] = curr_hsv[1] + PWM_STEP | |
-- If we reached the end | |
if curr_hsv[1] == 256 then | |
-- Go back to the beginning | |
curr_hsv[1] = 0 | |
-- or we reached the beginning | |
elseif curr_hsv[1] == -1 then | |
-- Go back to the end | |
curr_hsv[1] = 255 | |
end | |
-- Convert the HSV to RGB and set it/change the PWM output | |
curr_rgb = hsv_to_rgb( curr_hsv, PWM_MAX ) | |
set_rgb( curr_rgb ) | |
end | |
-- Start the hue fading | |
-- | |
-- interval: How many milliseconds (ms) there should be between each fade step (default: 100) | |
function hue_fade(interval) | |
interval = interval or 100 | |
-- We start at the beginning | |
-- You could potentially skip this to start from the current Hue | |
curr_hsv[1] = 0 -- Alternative: curr_hsv = {0,255,255} | |
-- Start the alarm/interval that runs the fade | |
tmr.alarm( FADE_ALARM, interval, 1, hue_fade_do ) | |
end | |
-- Just a nice wrapper to stop the fade | |
function fade_stop() | |
tmr.stop(FADE_ALARM) | |
end | |
---------------- | |
-- RGB FADING -- | |
---------------- | |
-- Set global variable for RGB (this is sort of unnecessary in Lua, but good form) | |
curr_rgb = {0,0,0} | |
fade_step = 1 | |
curr_color = 1 | |
-- Do the actually rgb fading | |
-- This just fades one step | |
-- It's in its own function to make it easier (and cleaner) to call with tmr.alarm | |
-- | |
-- Note: This goes through each channel and increases or decreases the values in sequence | |
-- The sequence is (We start and end [and loop around] on full red): | |
-- Green + | |
-- Red - | |
-- Blue + | |
-- Green - | |
-- Red + | |
-- Blue - | |
function rgb_fade_do() | |
-- Increase (or decrease) the current color channel with one step | |
curr_rgb[curr_color] = curr_rgb[curr_color] + fade_step | |
-- When the end is reached for current channel/color | |
if curr_rgb[curr_color] >= PWM_MAX then | |
-- Decrease the value in the next stage of fading | |
fade_step = -1 * PWM_STEP | |
-- Since steps can be bigger than 1, the current value can become too big, so we need to clamp it to max (1023) | |
curr_rgb[curr_color] = PWM_MAX | |
-- When the beginning is reached for the current channel/color | |
elseif curr_rgb[curr_color] <= 0 then | |
-- Increase the value in the next stage of fading | |
fade_step = PWM_STEP | |
-- Since steps can be bigger than 1, the current value can become negative, so we need to clamp it to 0 | |
curr_rgb[curr_color] = 0 | |
end | |
-- Did we change Red | |
if curr_color == 1 then | |
-- Now changee Blue | |
curr_color = 3 | |
-- Did we change Green | |
elseif curr_color == 2 then | |
-- Now change Red | |
curr_color = 1 | |
-- Did we change Blue | |
elseif curr_color == 3 then | |
-- Now change Green | |
curr_color = 2 | |
end | |
-- Change the RGB output | |
set_rgb( curr_rgb ) | |
end | |
-- Start the rgb fading | |
-- | |
-- interval: How many milliseconds (ms) there should be between each fade step (default: 100) | |
function rgb_fade(interval) | |
interval = interval or 100 | |
curr_color = 2 -- We start with fading up green | |
fade_step = PWM_STEP -- Initiate the step variable | |
curr_rgb = {1023,0,0} -- If we don't start with full red, it will fade up to full green and then crash | |
-- Start the fading | |
tmr.alarm(2, interval, 1, fade_do) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment