Skip to content
Michael Miller edited this page Jan 8, 2017 · 7 revisions

This section will describe how to implement a spiral topography for a concentric series of ring NeoPixels.

Today you can purchase rings of NeoPixels of various counts, with the most popular being 60, 24, 16, 12, and the jewel that is 6 pixel ring with a center pixel. As an example this section will discuss using all of these in one series with the jewel in the center.

How do you physically connect your pixel collection

The first thing to discuss is then how the rings get wired together. Is the first pixel the center pixel/ring? Is the outer ring contain the first pixel? I will discuss using the center as the first. Then you will want to make sure each ring is rotated so that the first pixel on each ring align near the same straight virtual line from the center to an outside edge. I will discuss using a starting line that is vertically straight up; or in the nomenclature of a clock, at 12 o'clock position.

The second thing to discuss is how you want to "address" the pixels. When talking about a Cartesian or matrix layout, you address the by the column (x) and row (y). But with a spiral its more about Ring (distance from center) and then Pixel (count from a standard position). You might even consider thinking of the Pixel as an angle which would then be similar to a polar coordinate system; but for this discussion we will keep it simple to the count along the ring from top center. Of course with this simplified model, the number of pixels per ring changes with the ring distance from center.

With this, each ring and its starting pixel index is listed in the next table. With the total number of pixels being 119.
Ring - Starting Pixel
0 - 0 (the center of the jewel)
1 - 1 (the outer ring of the jewel)
2 - 7 (the first on the 12 count ring)
3 - 19 (the first on the 16 count ring)
4 - 35 (the first on the 24 count ring)
5 - 59 (the first on the 60 count ring)

The Spiral Topology Object

If you examine the topology objects included in the library, they always include a constructor to configure the object and a method that maps the coordinates into the linear strip index that you then pass to the NeoPixelBus. We want to continue to follow this model; but use our addressing concept.
So it will include a constructor that assumes our configuration. You can always make this more general if you want.
It will also include a map method like

uint16_t Map(int16_t ring, int16_t pixel) const

Mapping Implementation

To implement the map method, the ring argument and pixel must be translated into the final index value. The first part is to calculate the start of the ring; this can be accomplished by a lookup table using the data above. Then we just need to add the pixel to offset to the correct one.

const uint16_t Rings[] = {0, 1, 7, 19, 35, 59}; 

uint16_t Map(int16_t ring, int16_t pixel) const {
  uint16_t index = Rings[ring] + pixel;
  return index;
}

This can be improved by checking boundary cases. As it sits if you call it with ring 0 but state pixel #32 which doesn't make sense; it will return a value that is possibly out of scope.

Below I have added the argument checking code. The ring table has been expanded to include an extra ring valueas it provides us with the information to be able to validate the arguments.

const uint16_t Rings[] = {0, 1, 7, 19, 35, 59, 119}; // note, included the start of another ring 

uint16_t Map(uint16_t ring, uint16_t pixel) const {
  if (ring >= (sizeof(Rings)/sizeof(Rings[0]) - 1)) { // minus one as the Rings includes the extra value
    return 0; // invalid ring argument
  }
  if (pixel >= Rings[ring + 1]) { // using the extra value for range testing
    return 0; // invalid pixel argument
  }
  uint16_t index = Rings[ring] + pixel;
  return index;
}

NOTE: The code above (sizeof(Rings)/sizeof(Rings[0]) is a way of having the compiler calculate the count of elements in the Rings array without us hardcoding anything. Often this is exposed as _countof() in many code based but it was not included in the Arduino headers for some reason.

Making the object usable for different collections of rings

The above code hard codes the array of values. We can make this a configuration argument to the constructor of our mapping class and thus reuse the code for different projects.

Clone this wiki locally