My new idea is about using color spaces in microcontrollers. The fact that my news doesn’t seem so to someone, I won’t be surprised at all. However, I propose a method and its implementation, the likes of which I have not met. A little introduction, where it all began. Most recently, the year ended, and I have about 300 ws2811 LEDs left. For some reason, their hands never reached them, but after celebrating the new year with relatives, I noticed that my aunt had a small Christmas tree made of glass, which she loves very much. But this time she didn’t sparkle at all and didn’t sparkle for a very banal reason ... batteries ran out. Remembering about his set of LEDs, he promised to make a small garland for this Christmas tree in order to eliminate these disadvantages once and for all and to make another person close to me a little bit happier.
Development spins in my head, gradually accelerating, sometimes turning into a whirlwind of thoughts, but to this day it has not moved from its place. I stopped by the fact that all those ideas that dealt with the effects program and their switchings looked very cumbersome and terribly inconvenient in my head when I implemented them in the RGB color space, and besides, I couldn’t decide which controller to use. But thanks to my little experience as a designer, I have knowledge of other spaces, which led me to think about HSL and HSV. There was no need to invent a new one, it is completely necessary to implement it, just search ... but I never found it. No, I certainly found a lot of interesting solutions, and many of them were yours, presented right here on Habré, and for which I am eternally grateful to all of you, you gave food for thought.
As a result, it was decided to stop at HSL, eliminating one component S (Saturation), leaving it constant (as in the implementation of the CDA, this parameter is superfluous), and the controller decided to use ATMega8U. ')
Sketching the code and testing the firmware, I realized that something was missing. And I did not have a little inconsistency to the real. Every hunter wants to know ... And again I return to this issue, and again realize that it is not ... No orange in nature, whatever one may say, it is just a shade.
This became the root and core of my idea, so I completely rewrote the code and came up with a method of using a compact description of the color space, and this is not HSL, HSV or RGB, because I expanded the orange range and brought it to a separate spatial sector.
Now only in essence
A space can be represented as the surface of a cylinder if it is closed, with base colors arranged in corners, shifted by 60 degrees, between which are filled with gradient transitions from one base color to another:
Where the height of the cylinder is the brightness, which is also a component of the gradient of shades. The same space represents a rectangle with sides “A x B”, where “A” is the angle defining the base color and its hue, and “B” is the brightness, in case the space is not closed or reversed from the cylinder.
For the task, I decided to use a square with a side of 256 x 256, thereby fitting into a type of bytes, where for an angle, the values vary within 0..255 (byte), and the brightness: -127..127 (sign byte), thanks to this got the opportunity to use 8 basic colors and 32 gradations of shades for each.
The space is described as an array with RGB components of the basic colors, and gradients of shades are calculated on the fly. Example description:
the sequence of basic colors is described here, let's call it simply - a rainbow, where the component order is changed according to ws2811 (GRB), and the latter is red, serves to close the space, it looks like this:
Now, if we take some color index, for example, 183, then we can make it be mapped into RGB space in the following way: Base color index = 183/32 = 5 (blue, or {0,0,128}) Offset hue = 183% 32 = 23 Now we calculate the difference between the components of the obtained base color ({0,0,128}) and the next {0,128,128} to calculate the increment (let's call it delta):
Since there are 32 gradations between colors, it is necessary to split the difference of components:
dG= 0 / 32 = 0, dR= 128 / 32 = 4, dB= 0 / 32 = 0;
Now it is necessary to multiply the resulting delta by the hue shift and add to the components of the current base color ({0,0,128}):
G = 0 + 0 * 23 = 0, R = 0 + 4 * 23 = 92, B = 128 + 0 * 23 = 128;
Received {0,92,128}, to which you can now add brightness, for example 50: {50,142,178} - the desired color.
As you can see from the example, there is nothing complicated. If the delta takes a negative value, then the shift of the hue when multiplying gives a negative addition, which together will give the difference with the final component, this will happen when the gradient component declines.
And yes, the brightness is not applied proportionally, but linearly, which gives some error, but in the framework of the described task it is not terrible. It is also necessary to monitor the lower threshold in order not to cause overflow at negative brightness.
Thus, the resulting space allows the use of 256 * 256 = 65536 shades.
The whole described method was optimized, all multiplications and divisions were eliminated, and reduced to simplified fast math, it makes it easy to rewrite it to assembler (I personally absolutely like the speed: 200 ns between calculations for 7 LEDs) if optimization is needed to reduce the size code. As a result, the initial decision on the use of ATMega8u was canceled in favor of Tinky, since the resulting firmware took less than half a kilobyte.
No, I'm not a goon, I'm just lazy.
So, the code below performs initialization, and contains the function of outputting data to the ws2811 line:
And this is the code of the main loop and the function of converting the space index into color components with an example of use:
But a video with a demonstration of work (though the colors are far from reality):
It makes no sense to describe in more detail the comments in the text, the method itself is already described above. If you have questions, ask.
Well, actually, that's all I wanted to tell.
... joke - not all!
Let's now imagine for a second how this method can be used. As you, for example, such a space, which is shown in this picture:
Right! You can describe different colors and gradients between them. And who decided at the beginning of the description that the method does not allow the use of shades of gray is wrong.
In conclusion - the most delicious
An array describing the space in memory occupies 32 bytes (due to alignment), which is stored in program memory. If you create several such descriptions of spaces, and switch between them on the fly, switching the pointer to the array, you can select the current space for each new LED in the queue, as well as expand the number of gradations between shades. And if we slightly modify the code, and increase the number of steps in the description of the components to 8, then 32 * 8 will give 256 (in my code, 3 steps), then using seven arrays (0..255 red, yellow, green, blue, blue , purple, white) give a combination of 16m shades "simultaneously on the screen"! When using 224 bytes of firmware for storage. And if a little more to sweat and just rewrite the code, then you can fit in 96 bytes with the same result.
Plus, thanks to the optimization and the resulting processing speed, you can use the so-called dithering method ((English dither from old English didderen - shake) With the help of which, you can achieve an even greater number of shades (just think: 512 * 512 * 512 = 134.217. 728). It also allows you to create closures of spaces on each other in order to eliminate or make invisible transition effects. In general, everything offered can be modified as you please, to your taste and color, and can be used as is, and pleasure I I guarantee you!
02.26.2015: I propose to download the source code for free use.
02/27/2015: An updated version of code v1.2 is available. The changes are: 1. The base color is encoded by bits of one byte: 0bxxGGRRBB, the array is reduced to 8 bytes. 2. Changed the conversion function in accordance with claim 1., the code has become a bit more (20-30 bytes) In the future, I plan more changes, who are interested - do not miss it!
02/28/2015: Last code update to version v2.0 Changes: 1. Added work with subspaces, introduced 8x8 space (separate selection of 65536 shades for subspaces with switching)
2. Added an example of choosing a color subspace (in the demo example, switching to the next subspace occurs when the lower brightness threshold is reached) 3. The size of the code has grown to 626 bytes (64 bytes - the space itself + 16 bytes for pointers, total - 80 bytes, the rest - example code: 188 bytes). Thus, more than 500 bytes of additional code are available for organizing the effects program. 4. Slightly optimized code. 5. Profiling was not performed.
SUMMARY: 5 free legs of tinky, allow you to apply several separate channels for independent display on the ws2811 lines of color effects, and realize control over them through hardware input or in automatic mode. The implemented buffer (14 LEDs) allows you to use a line of LEDs a multiple of this number, and transfer data to the next section by repeating the transfer buffer.
There will be no more updates in this direction; we will consider the algorithm to be complete, although ideas seem inexhaustible. I suggest you develop your own development in this direction. For example: you can implement a smooth change of color subspaces (teleportation), change the brightness control algorithm to the correct one, enter the color saturation parameter, and much more, which is missing here. Good luck to all delights and accomplishments, with the upcoming spring!
I look forward to your comments, critics, questions and advice. Thank you for your attention, see you soon!
Use in commercial projects, resale of the source code, use for the purpose of profit and any mercenary purposes is prohibited. Source texts are distributed free of charge as is, in case of use on other sites or in other sources, the indication of the author and notification of placement is mandatory.