I was somewhat inspired by a post by scottiebabe - Electronic Die to look at square NeoPixel Arrays. I've used NeoPixels in PCB strips and rings and also flexible strips before, but haven't tried square arrays. It seems the common square matrix PCB sizes are 4x4 or 8x8 using WS2812B RGB LEDs.
I found an interesting option on Amazon - ALITOVE 100pcs WS2812B Addressable 5050 Smart RGB LED Pixels. It is a 10x10 matrix of RGB LEDs that are physically, but not electrically connected That supposedly would allow you to create any square or rectangular matrix smaller than 10x10. Bad news is that you need to do all the wiring (3pins between the LEDs). For $15.99, I thought I would give it a try.
Here's what it looks like - just under 100mm square:
And the pads that need to be connected on the back:
I cut out a 3x3 section to make an electronic dice face. The PCB is pre-scored between RGB LEDs, but I learned the hard way that trying to cut a square section out with diagonal cutters puts force in the wrong places and will cause adjacent score lines to break. It's a little to hard to cut with an X-ACTO knife. I think I'll need to appropriate a rotary cutter from my wife for future cutting.
I used the QWIIC port on the RP2040 Feather to connect to the array. Connecting the LED pads wasn't too bad using 28AWG wire.
Here's a quick video:
The code is brute force, I probably need to improve it to use in a project.
NeoPixel_Array_Dice.ino
// NeoPixel Array simple sketch (c) 2022 ralphjy // Released under the GPLv3 license to match the rest of the // Adafruit NeoPixel library #include <Adafruit_NeoPixel.h> // Which pin is connected to the NeoPixels? #define PIN 2 // QWIIC connector SDA // How many NeoPixels? #define NUMPIXELS 9 // 3x3 array size // When setting up the NeoPixel library, we tell it how many pixels, // and which pin to use to send signals. Note that for older NeoPixel // strips you might need to change the third parameter -- see the // strandtest example for more information on possible values. Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); #define DELAYVAL 1000 // Time (in milliseconds) to pause between pixels void setup() { pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) } void loop() { pixels.clear(); // Set all pixel colors to 'off' for (int a = 1; a < 7; a++) { diceNumber(a); delay(DELAYVAL); // Pause before next pass through loop } } void diceNumber(int n) { switch (n) { case 0: pixels.setPixelColor(0, pixels.Color(0, 0, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 0, 0)); pixels.setPixelColor(3, pixels.Color(0, 0, 0)); pixels.setPixelColor(4, pixels.Color(0, 0, 0)); pixels.setPixelColor(5, pixels.Color(0, 0, 0)); pixels.setPixelColor(6, pixels.Color(0, 0, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 0, 0)); pixels.show(); break; case 1: pixels.setPixelColor(0, pixels.Color(0, 0, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 0, 0)); pixels.setPixelColor(3, pixels.Color(0, 0, 0)); pixels.setPixelColor(4, pixels.Color(0, 20, 0)); pixels.setPixelColor(5, pixels.Color(0, 0, 0)); pixels.setPixelColor(6, pixels.Color(0, 0, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 0, 0)); pixels.show(); break; case 2: pixels.setPixelColor(0, pixels.Color(0, 20, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 0, 0)); pixels.setPixelColor(3, pixels.Color(0, 0, 0)); pixels.setPixelColor(4, pixels.Color(0, 0, 0)); pixels.setPixelColor(5, pixels.Color(0, 0, 0)); pixels.setPixelColor(6, pixels.Color(0, 0, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 20, 0)); pixels.show(); break; case 3: pixels.setPixelColor(0, pixels.Color(0, 20, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 0, 0)); pixels.setPixelColor(3, pixels.Color(0, 0, 0)); pixels.setPixelColor(4, pixels.Color(0, 20, 0)); pixels.setPixelColor(5, pixels.Color(0, 0, 0)); pixels.setPixelColor(6, pixels.Color(0, 0, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 20, 0)); pixels.show(); break; case 4: pixels.setPixelColor(0, pixels.Color(0, 20, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 20, 0)); pixels.setPixelColor(3, pixels.Color(0, 0, 0)); pixels.setPixelColor(4, pixels.Color(0, 0, 0)); pixels.setPixelColor(5, pixels.Color(0, 0, 0)); pixels.setPixelColor(6, pixels.Color(0, 20, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 20, 0)); pixels.show(); break; case 5: pixels.setPixelColor(0, pixels.Color(0, 20, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 20, 0)); pixels.setPixelColor(3, pixels.Color(0, 0, 0)); pixels.setPixelColor(4, pixels.Color(0, 20, 0)); pixels.setPixelColor(5, pixels.Color(0, 0, 0)); pixels.setPixelColor(6, pixels.Color(0, 20, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 20, 0)); pixels.show(); break; case 6: pixels.setPixelColor(0, pixels.Color(0, 20, 0)); pixels.setPixelColor(1, pixels.Color(0, 0, 0)); pixels.setPixelColor(2, pixels.Color(0, 20, 0)); pixels.setPixelColor(3, pixels.Color(0, 20, 0)); pixels.setPixelColor(4, pixels.Color(0, 0, 0)); pixels.setPixelColor(5, pixels.Color(0, 20, 0)); pixels.setPixelColor(6, pixels.Color(0, 20, 0)); pixels.setPixelColor(7, pixels.Color(0, 0, 0)); pixels.setPixelColor(8, pixels.Color(0, 20, 0)); pixels.show(); break; } }