How can I code a non-square pixel grid?

Status
Not open for further replies.

Jay Converse

Active member
I'm building a thing with my Teensy with WS2812 strips in a grid, but the grid isn't a regular X by Y square matrix. The pixels will fill a circle. There are 9 strip segments, and from top to bottom the number of pixels on the strips are 8, 14, 16, 18, 18, 18, 16, 14, 8. Technically it's an 18X18 matrix, but the 4 corners have "dead" pixels.

It's looking really good when I hand-code the numbers from an Excel spreadsheet, I can do moving rows, columns, and diagonals, but it's really tedious to code.

I've looked over the OctoWS2811 and Adafruit_NeoMatrix libraries, but they expect a square matrix.

Any ideas?
 

Attachments

  • 20160526_182809.jpg
    20160526_182809.jpg
    115 KB · Views: 182
Can you pretend it really is a square matrix (of a size that the circle just fits inside) for purposes of data storage, and then crop out or select just the active area of each line when you send the values out to the pixel strip?

The key would be a boolean function inCircle(x,y) that is true for all physical pixels (inside the circle shape). Your readout process has logic that says
Code:
for (x=0;x<xsize;x++)
 for (y=0;y<ysize;y++)
  if inCircle(x,y) send out pixelValue(x,y);  // ...and if not, just move to the next pixel

but I don't know how these things work, maybe it's all DMA driven from packed memory block and not a simple loop like that?
 
Last edited:
Yup, that's exactly what I'm trying to do. But as a lazy coder I'd like to use the existing libraries, and they all assume when they're sending the bits that the matrix is neat and square.
 
Can you have your own intermediate storage, in a larger matrix that contains the circle, then transfer that to the packed output matrix using the logic above, so you can use the existing library? Or does that not work because each horizontal line is addressed separately? I was somehow assuming the entire string was logically contiguous.
 
Yeah, that's exactly the trick I'm trying to figure out, to send a partially filled matrix. Maybe I need a mapping array. Fill the full square, then the array only sends the "live" pixels mapped to the real addresses on the string. I'm sure I can code it, but right now I'm just being lazy. ;-)
 
I think JBeale has the tip to get there - program in the square pixelValue then:

Code:
int ii = 0;
for (x=0;x<xsize;x++)
 for (y=0;y<ysize;y++)
  if inCircle(x,y) LEDS[ii++]=pixelValue(x,y);  // ...and if not, just move to the next pixel

// WRITE LEDS[]

Code:
int8_t inCircle[5][5] = {
0,0,1,0,0,
0,1,1,1,0,
1,1,1,1,1,
0,1,1,1,0,
0,0,1,0,0,
}
 
Last edited:
If you have truly absurd pixel gemoetry (had a neopixel ring with hand fitted infill) you can also do the reverse, have an array with an entry for each pixel in the chain that contains a pairs of pre computed X/Y values to stick into procedural pattern generation or finding the nearest 'fit' for bitmap or other matrix load. Will be slower than the above of course, but can often avoid floating point math at run time.
 
Guess what? Adafruit has a "setRemapFunction", where you can roll your own mapping function. Because my grid goes left-to-right on one row and right-to-left on the other, my function is ugly, but it gets the job done:

matrix.setRemapFunction(circleGrid);


uint16_t circleGrid(uint16_t x, uint16_t y) {
// Brute force address conversion, XY to live pixels

y = y *18 + x; // Compute index number based on 9x18 matrix

if(y>36 && y<53)
{
return y-37;
}
if(y>71 && y<90)
{
return y-38;
}
if(y>108 && y<125)
{
return y-39;
}
switch(y)
{
case 54:
return 33;
case 55:
return 32;
case 56:
return 31;
case 57:
return 30;
case 58:
return 29;
case 59:
return 28;
case 60:
return 27;
case 61:
return 26;
case 62:
return 25;
case 63:
return 24;
case 64:
return 23;
case 65:
return 22;
case 66:
return 21;
case 67:
return 20;
case 68:
return 19;
case 69:
return 18;
case 70:
return 17;
case 71:
return 16;

case 90:
return 69;
case 91:
return 68;
case 92:
return 67;
case 93:
return 66;
case 94:
return 65;
case 95:
return 64;
case 96:
return 63;
case 97:
return 62;
case 98:
return 61;
case 99:
return 60;
case 100:
return 59;
case 101:
return 58;
case 102:
return 57;
case 103:
return 56;
case 104:
return 55;
case 105:
return 54;
case 106:
return 53;
case 107:
return 52;

}


return -1;
}
 
Update.

There are two options for the Adafruit_NeoMatrix object, NEO_MATRIX_PROGRESSIVE and NEO_MATRIX_ZIGZAG. My grid is a zigzag! So, my remap function is now much smaller, no case statements needed.

RTFM...
 
I'm finally running. I have learned that when you have a setRemapFunction defined, some of the parameters to Adafruit_NeoMatrix are ignored.

For example, NEO_MATRIX_BOTTOM/TOP, NEO_MATRIX_LEFT/RIGHT, and even NEO_MATRIX_ZIGZAG/PROGRESSIVE are all ignored.

My circular grid starts on the bottom left, and zigzags to the top right. To make it work, I defined a simple array:

const int16_t TubaGrid[] = {
-1,-1,-1,-1,-1,218,219,220,221,222,223,-1,-1,-1,-1,-1,
-1,-1,-1,217,216,215,214,213,212,211,210,209,208,-1,-1,-1,
-1,-1,196,197,198,199,200,201,202,203,204,205,206,207,-1,-1,
-1,195,194,193,192,191,190,189,188,187,186,185,184,183,182,-1,
-1,168,169,170,171,172,173,174,175,176,177,178,179,180,181,-1,
167,166,165,164,163,162,161,160,159,158,157,156,155,154,153,152,
136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,
135,134,133,132,131,130,129,128,127,126,125,124,123,122,121,120,
104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,
103,102,101,100,99,98,97,96,95,94,93,92,91,90,89,88,
72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,
71,70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,
-1,42,43,44,45,46,47,48,49,50,51,52,53,54,55,-1,
-1,41,40,39,38,37,36,35,34,33,32,31,30,29,28,-1,
-1,-1,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,
-1,-1,-1,15,14,13,12,11,10,9,8,7,6,-1,-1,-1,
-1,-1,-1,-1,-1,0,1,2,3,4,5,-1,-1,-1,-1,-1
};

Then my function is merely:

uint16_t circleGrid(uint16_t x, uint16_t y) {

y = y * 16 + x; // Compute index number based on 16x17 matrix

return TubaGrid[y];
}


Here's the bench test.

I can't wait to take my sousaphone out for tonight's walk!
 
This looks amazing. With less than 255 pixels you can shrink the array to uint8_t if you replace -1 by 255 in TubaGrid[] and change circleGrid() to return -1 if TubaGrid[y]==255.

Is the animation stored frame-by-frame or generated by sprites that are shifted across the pixel grid?
 
Thanks, great idea on the uint8_t! That makes it half the size. I was going to add 3 more arrays this morning that are 90 degree rotations so I can use the same loops to move left, right, up, or down, so more memory space is good.

And you're right, these are all sprites that I shift across the grid.
 
Might be more efficient to just access the array with another version of circleGrid() instead of storing four arrays that essentially hold identical information. For 180° I think it's something like y = (16-y)*16 + (16-x) ?
 
Yeah, good idea, but it's not square. When I built it it ended up at 16x17, I must have bodged a measurement somewhere. The next one I build will be definitely square, and your formula would be perfect for that.
 
Haha, that is awesome! Once my sound project is up and running I was thinking about juicing things up a bit. This is great input for my brain coming up with more strange ideas :D
 
Status
Not open for further replies.
Back
Top