Advanced Segment Usage - AlbertGBarber/PixelSpork GitHub Wiki

This page covers aspects of segment sets that are not essential for understanding or using segment sets, but may be useful when tackling certain LED configurations.

Jump to:

Changing Segments:

Segments and Segment Sets are not stored in program memory, so it's possible to change a set's segments during runtime, either by swapping in/out different segments, or by swapping in/out the sections in a segment. While most effects should be flexible enough to keep working after a swap, overall I recommend you avoid doing so because such actions are not well tested. It is generally safer to have multiple whole segment sets, and swap them into and out of effects (although you must still check if the effect needs to be reset()).

If you must swap segments around you should:

  • Use the segment set's setSegment() function to change segments. This automatically re-calculates various segment dependent vars for the segment set.

  • If you change the number of segments in the set, you must call calcSetVars() to re-calc various segment set vars.

  • If you change the length of a segment (by swapping a section), you must call the segment's getSegTotLen() function AND the segment set's calcSetVars() function to re-calc various settings. Remember that sections are read only, so you cannot change their properties.

Also note, that calling setSegment() or calcSetVars() will re-size the segProgLengths array, see Segment Set Reference Vars. The array is allocated dynamically and follows typical Pixel Spork dynamic allocation rules. See Managing Dynamic Memory and Fragmentation for more.

Reversed Sections:

This only applies to continuous sections! Sometimes, you have a section that needs to be reversed to fit into a segment set, see here, for example. To save memory, sections don't have a direction setting, instead you can set their lengths to be negative. A negative length makes the section count in reverse from the starting LED. So if I had a section like:

//A section that starts at LED 0, with length 12, ending at LED 11
const PROGMEM segmentSecCont mySec = {0, 12}; 

I could reverse it like:

//Reverse of the above section
//We start the section at 11, with a length 0f -12, so it ends at 0
const PROGMEM segmentSecCont mySec_reversed = {11, -12}; 

I reverse the first section (that starts at LED 0, with length 12, ending at LED 11) by setting the starting LED to the end LED and making the length negative. Ie, the starting LED is 11, and the length is -12. Now, when I ask for the first LED in the section, I will get 11, then 10, 9, etc.

Remember, sections are read only! You can't modify a section while the program is running!

Dummy LEDs:

Note that Pixel Spork uses an internal D_LED_PS constant. THIS IS FOR INTERNAL USE ONLY AND IS NOT RELATED TO DUMMY LEDS!

Sometimes you may have a segment set with segments of multiple lengths, but you'd like them to behave as if they were all the same length. For example, consider the LED shutter shades pictured below. Assume that I've created a segment set that groups the LED rows together as individual segments. The top two rows and the bottom three rows have two different lengths.

If I wanted to scroll a pattern horizontally across the shades using the segment set lines, it would be distorted because of the different row lengths. To fix this, I need to make the rows all the same length. One method would be to omit the center LEDs from the top two row segments, but then the scroll pattern would jump across the eye bridge. Instead, I can add "dummy" LEDs to the bottom three rows. "Dummy" LEDs are LEDs that the code thinks exist, but don't actually exist in real life. To used dummy LEDs, we need to add them to the FastLED "leds" array, and then add them to our segments.

Looking at the shades, the top two rows are 17 LEDs long while the bottom 3 are only 14 LEDs, so the bottom rows will need 3 dummy LEDs. For this example, I'll share the dummy LEDs between the bottom rows. The shades have 76 LEDs in total, but I'll increase this to 79 for the extra dummy LEDs. Since the shades are wired in rows, I'll use continuous sections for each row. The bottom three rows will have 3 sections -- one section for each eye, and a center section for the dummy LEDs. The resulting code looks like:

//Create the color storage array used for the LEDs, 
//note that the shades have 76 LEDs, but I've made space for 79
//The 3 extra spots are for the dummy LEDs
CRGB leds[79];

//Create segments for each row, adding 3 dummy LEDs to the last 3 rows to make them equal the first two
const PROGMEM segmentSection rowSec0[] = { {0, 17} }; //row 0, 17 pixels
SegmentPS rowSegment0 = { SIZE(rowSec0), rowSec0, true }; 

const PROGMEM segmentSection rowSec1[] = { {17, 17} }; //row 1, 17 pixels
SegmentPS rowSegment1 = { SIZE(rowSec1), rowSec1, false }; //facing reverse direction from top row

const PROGMEM segmentSection rowSec2[] = { {34, 7}, {76, 3}, {41, 7} }; //row 2, 17 pixels, with 3 dummy pixels in the center
SegmentPS rowSegment2 = { SIZE(rowSec2), rowSec2, true }; 

const PROGMEM segmentSection rowSec3[] = { {48, 7}, {76, 3}, {55, 7} }; //row 3, 17 pixels, with 3 dummy pixels in the center
SegmentPS rowSegment3 = { SIZE(rowSec3), rowSec3, false }; //facing reverse direction from top row

const PROGMEM segmentSection rowSec4[] = { {62, 7}, {76, 3}, {69, 7}}; //row 4, 17 pixels, with 3 dummy pixels in the center
SegmentPS rowSegment4 = { SIZE(rowSec4), rowSec4, true }; 

//Create the segment set using the row segments
SegmentPS *rows_arr[] = { &rowSegment0 , &rowSegment1, &rowSegment2, &rowSegment3, &rowSegment4 };
SegmentSetPS rowSegments( leds, NUM_LEDs, SIZE(rows_arr), rows_arr );

In the code above, I first create the CRGB color storage array, leds, with space for the shade's 76 LEDs plus the 3 dummy LEDs. For dummy LEDs to work, the code has to think they really exist, so we must make space for them. Otherwise, the code will try to write/read from the LEDs and crash.

Next I create each of the shade's segment rows. Looking more closely at the row 2 segment section array: { {34, 7}, {76, 3}, {41, 7} }, we see it is made up of 3 sections. The first and last sections ( {34, 7} and {41, 7} ) are the two halves of the real row, split in two to add the center dummy section. The middle section ( {76, 3} ) is the dummy LEDs. This section is in the center of the segment so that it aligns with the "missing" LEDs on the shades. Note that the section starts at LED 76, which is greater than the number of LEDs on the shades (the shades have 75 LEDs counting from 0). Dummy LEDs must always come after the all the real LEDs. When the code writes out to the segment set, it will send out data to the dummy LEDs, but because they don't exist, the data will be pushed off the end of the strip and discarded.

Looking at the other row segments, we can see that the dummy LED section is repeated in segment rows 3 and 4, using the exact same dummy LEDs. Reusing the same LEDs will be fine for most effects. Although the LEDs are virtual, the code still thinks they're real, so, it can write/read colors to them as normal. However, because the LEDs are shared between the 3 segments, whenever an effect changes their colors, it will be changing them for all the segments. Obviously, because the LEDs don't physically exist, sharing them doesn't matter for drawing, but some effects (usually those with "fast" in their names) need to read colors from LEDs, so sharing them may cause the effects to malfunction. The easiest solution is to use unique dummy LEDs for each segment. This would require using 9 dummy LEDs (3 LEDs for each segment), which takes up more memory, but is almost certainly worth it, given the small number of LEDs on the shades. I leave this correction up to the reader.

Hopefully that example made sense, it basically covers all you need to know about dummy LEDs.

Buffer LED:

Occasionally, particularly when working with 3.3V micro-controllers, it is useful to have a "buffer" LED at the start of your strip to help stabilize you data signals. This LED is not included in effects, and is only meant for passing data. While I recommend using a proper shift-register or the diode drop trick to stabilize signals, it's easy enough to add a "buffer" LED using segments. Simply start all your segments at LED 1, and then write 0 color to the buffer LED sometime during your setup. Since the LED is not in any segments, it won't ever be lit.

Brightness:

Each segment set has a brightness setting, brightness, which controls the set's brightness relative to the FastLED global brightness ( set using FastLED.setBrightness() ). Both FastLED's brightness and a segment set's brightness have a range of 0 to 255, with 255 being maximum brightness. Because the segment brightness is relative, if you set a set's brightness to 127, it will be half as bright as the overall strip. In other words, if your FastLED brightness is set to 80, and you set your set's brightness to 127, the resulting brightness for the set will be 40 -- half as bright as the FastLED 80 brightness setting.

You can set a segment's brightness directly as yourSegmentSet.brightness = x;.

Brightness is intended to be used as more of a global adjustment setting, rather than an animation effect.

Note that the effect set fader utility does actively manipulate a segment set's brightness. It is used to fade effects in/out as you change effects (like how a song fades out at the end). It should reset a segment set back to its original brightness when the fade is finished.

Note that brightness is applied whenever a color is written to a pixel (not when the pixel is actually shown). A special function segDrawUtils::handleBri() is called on the pixel to dim its color. This destructively changes the pixel's color in the FastLED leds color storage array. If an effect needs to read colors from the array, it will read the dimmed colors. I try to avoid doing this because there is no way to recover the original color. However reading colors is needed for some effects, mainly those with "fast" in their names and also the Pride W/ Pal, Pacifica, and Soft Twinkle effects. These effects may look wrong if you have a dimmed segment set.

While I would like to have all effects work well with brightness, the only alternative I can think of is to set the brightness of all the LEDs when you call FastLED.show(), which is the last step before writing them out. Although this seems like a great solution, because the LED colors would only be modified right before they are drawn, it does create a number of problems. Firstly, most effects don't touch all the LEDs every update cycle, leaving most LEDs the same color over multiple updates. This means they will have their color dimmed multiple times. This issue becomes even worse if you are using multiple effects, because each effect show() will also dim the LEDs. To fix this, you'd need some sort of global flag array to mark whether an LED had been dimmed, but that would require a lot of extra memory, and just doesn't seem worth it given all you get is fading.

Multiple Effects On One Segment Set:

Not to be confused with multiple effects on multiple segment sets, which is totally fine.

While it is possible to run multiple effects on one segment set at the same time, they may not work well together. Effects are not written with other effects in mind and so there is no sort of overwrite protection, or syncing system. In other words, effects expect to be the only thing changing a segment set's pixel colors. If you do try to run multiple effects, you should try to run them all at the same update rate, which should help minimize overwriting.

Note that the Add Glitter utility is an effect that is designed to run alongside other effects. It randomly twinkles the segment set's pixels.

Single Segment Sections:

Occasionally you may encounter shapes where you'd like to treat a line of pixels as a single pixel. This is supported by the section single setting. You can configure a section to be "single" by passing an additional bool argument like so:

const PROGMEM segmentSecCont mySingleSec = {0, 24, true};

This works the same for both mixed and continuous sections. The final true argument sets the section to be "single".

Note that you cannot modify sections after they've been created, so you cannot flip a section from single to not single.

As far as segment sets and effects are concerned, a "single" section is a single LED -- it being the starting LED for continuous sections, or the first LED in the section array for mixed sections. When the "single" LED is colored, the color is copied to all the other "hidden" LEDs in the section. In other words, if I have a line of 10 LEDs that I place into a "single" section, all 10 of those LEDs will always be the same color and will be treated as a single LED by all effects and segment set functions.

You can see an example usage of "single" sections on the Rings Segment Examples page.

Segments Sets Are Secretly a Matrix (and can you transpose them?):

When you build a segment set, you are secretly creating a matrix with dimensions (number of segments) x (number of segment lines). This matrix is not stored in code directly, rather it is constantly worked out on the fly whenever you ask what pixel is at x segment and y line number (see the segment drawing functions). Be aware that since segment sets don't need to have equal segments, a single pixel can be a member of multiple segment lines. This information is probably only useful if you'd like to program an effect, but it does lead to an important question.

Can I transpose a segment set (swap its matrix rows and columns) during runtime?

Unfortunately, while it should be technically possible to do this, I believe doing so will add quite a bit of complexity/computations, and so I have not attempted to do it at this current time.

So currently, if you want to transpose your segment set, you need to create a separate segment set to do so. This may seem tedious, but most effects use a single color when drawing segment lines, so you can use a trick with "single" sections to quickly transpose a segment set.

Consider the rings segment example. Lets say that we have an effect that is drawn on the ring's segment lines, but we want to transpose it to draw on whole rings instead. While we could go through and create a new segment set to (made up of the ring's segment lines as segments), we could instead use "single" sections to mark each ring. Each "single" section is treated as a single LED, with the color being copied to all other LEDs in the section. By storing each ring as a "single" section in a single segment, we have effectively transposed our rings segment set. This results in the code below:

//All the rings in one segment, with each ring being treated as one pixel
const PROGMEM segmentSecCont ringSingleSec[] = { {0, 24, true}, {24, 16, true}, {40, 12, true}, {52, 8, true}, {60, 1} };
SegmentPS ringSingleSeg = { ringSingleSec, SIZE(ringSingleSec), false };

SegmentPS *ringsSingleSeg_arr[] = { &ringSingleSeg };
SegmentSetPS ringSingleSet( leds, NUM_LEDs, ringsSingleSeg_arr, SIZE(ringsSingleSeg_arr) );

Effects see the ringSingleSet as a single segment with 5 pixels, one for each ring. Since the set is 1D, the segment lines are just the pixels themselves. Meanwhile because the sections are "single", each whole segment will be filled in whenever an effect sets a segment line color. Most effects use the same color for whole segment lines, so the result will be the same as if we fully transposed the segment set, while using much less code and memory.

Note that this method will not work with most color modes, or with any effect that varies colors along segment lines.

Color Modes:

See color modes.