Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@ChrisHinde
Last active August 27, 2016 16:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChrisHinde/b15ca6199c7dec46e8b26f0a60af399c to your computer and use it in GitHub Desktop.
Save ChrisHinde/b15ca6199c7dec46e8b26f0a60af399c to your computer and use it in GitHub Desktop.
Functions to fade between colors with Lua on NodeMCU
--[[ 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