Introduction: Custom 16x8 Ws2812 Matrix Project

About: Hello world;

Hey guys how you doing?

So here's something extraordinary, a custom Matrix entirely made from scratch.

This Instructables is about the whole built process of this matrix and its use.

also, here's the tindie page if you want to get this display- https://www.tindie.com/products/maepa/neopixel-display-matrix/

Supplies

Following are the things used in this built-

  • WS2812B LEDs
  • Custom PCB
  • Arduino Nano
  • Breadboard
  • Jumper wires
  • Solder Paste

Step 1: Basics

This Matrix is in 16x8 configuration and is laid out in an OXPLOW layout, OXPLOW is a matrix type in which LEDs go one way in one row, and then backward in the next row, and so on, this layout is also called boustrophedon.

There's also another layout which is the serpentine Layout and LEDs in this layout are laid out in a continuous chain like a snake thus the name serpentine.

This Matrix is controlled via the FAST LED Library but can be operated with a bunch of existing libraries like ADAFRUIT's Neopixel Library or SmartMatrix Library etc.

Step 2: WS2812B

WS2812B is an intelligent control LED light source that the control circuit and RGB chips are integrated into a package of 5050 components. It internal includes an intelligent digital port data latch and signal reshaping amplification drive circuit

The data transfer protocol uses a single NZR communication mode. After the pixel power-on reset, the DIN port receives data from the controller, the first pixel collects initial 24bit data and then sent to the internal data latch, and the other data which reshaping by the internal signal reshaping amplification circuit sent to the next cascade pixel through the DO port.

Its operating voltage is between +3.5~+5.3V DC.

Checkout its datasheet for more info- https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf

Step 3: PCB Design

We start first with the schematic that consists of 128 RGB LEDs connected back and forth in the OXPLOW layout. There's a CON4 Header Pin that is connected with VCC, GND, Din, and Dout Pins and three different Pins for VCC, GND, and Din.

All LED's VCC and GND are connected in Parallel.

Dout of the 1st pixel goes to the Din of the 2nd pixel, Dout of the 2nd pixel goes to the Din of the 3rd pixel and this goes on and on up to the 128th pixel.

See the attached PDF for a more clear image of the Board Schematic.

Each WS2812 LED requires a 0.1uF Capacitor to work properly but due to space issues, Capacitor was not added.

Practically, this board works fine but if it had some issues, we can add an external capacitor with VCC and GND.

Attachments

Step 4: Seeed Fusion

After finalizing the PCB and generating the Gerber data, PCBs were sent it to SEEED STUDIO for samples.

Order was placed for White Soldermask with Black Silkscreen and PCBs arrived in less than a week which was super fast.

The quality was super good considering the rate which was also pretty low.

Seeed Fusion PCB Service offers one-stop prototyping for PCB manufacture and PCB assembly and as a result, they produce superior quality PCBs and Fast Turnkey PCBA within 7 working days.

Seeed Studio Fusion PCB Assembly Service takes care of the entire fabrication process from PCB manufacturing, parts sourcing, assembly, and testing services, so you can be sure that they are getting a quality product.

After gauging market interest and verifying a working prototype, Seeed Propagate Service can help you bring the product to market with professional guidance and a strong network of connections.

Step 5: PCB ASSEMBLY

  • Solder paste dispensing
  • Pick and place
  • Hotplate Reflow

Step 6: SOLDER PASTE DISPENSING

We first apply solder paste to each component Pad one by one.

This Project uses WS2812B LEDs that has four Pads each, 128 LEDs were used so we need to apply solder paste to 512 Pads.

Instead of doing this manually, a stencil could be used here instead.

Step 7: Pick and Place Process

Next, we pick 128 LEDs and place them one by one in their assigned place.

Make sure to check the LED alignment and orientation as placing them correctly is crucial.

Step 8: HOTPLATE REFLOW

After the "Pick & Place Process", I carefully lifted the whole circuit board and place it on my DIY SMT Hotplate.

the hotplate heats the PCB from below up to the solder paste melting temp, as soon as the PCB reaches that temp, the solder paste melts and all the components get soldered to their pads, we lift the PCB and then place it on a cooler surface for a little bit, to cool down the heat of PCB.

Step 9: Arduino Nano Connection

To Drive the Matrix board, we need to connect an Arduino Nano to the matrix by following the above wiring Diagram.

  • VCC of Matrix will get connected to 5V of Nano
  • GND to GND
  • Din of Matrix to D9 (any PWM Pin)

We Solder Jumper Wires directly with VCC, Din, and GND pins of Matrix so we can connect Matrix with Arduino Nano through a breadboard.

Step 10: Library and Example Sketch

For using this board, we can utilize a bunch of existing Libraries but we will focus on FASTLED LIBRARY for now.

The FASTLED is a library used for controlling a wide variety of LED chipsets, like the ones sold by adafruit (Neopixel, DotStar, LPD8806), Sparkfun (WS2801), and aliexpress.

Here's its GitHub page and you first need to download and install the library in Arduino IDE. https://github.com/FastLED/FastLED

Here's the main sketch that we will use and it's called NoisePlusPalette which can be found in FASTLED Example Sketches.

#include <FastLED.h>

#define LED_PIN 9
#define BRIGHTNESS 96
#define LED_TYPE WS2811
#define COLOR_ORDER GRB

const uint8_t kMatrixWidth = 16;
const uint8_t kMatrixHeight = 8;
const bool kMatrixSerpentineLayout = false;


// This example combines two features of FastLED to produce a remarkable range of
// effects from a relatively small amount of code. This example combines FastLED's
// color palette lookup functions with FastLED's Perlin/simplex noise generator, and
// the combination is extremely powerful.
//
// You might want to look at the "ColorPalette" and "Noise" examples separately
// if this example code seems daunting.
//
//
// The basic setup here is that for each frame, we generate a new array of
// 'noise' data, and then map it onto the LED matrix through a color palette.
//
// Periodically, the color palette is changed, and new noise-generation parameters
// are chosen at the same time. In this example, specific noise-generation
// values have been selected to match the given color palettes; some are faster,
// or slower, or larger, or smaller than others, but there's no reason these
// parameters can't be freely mixed-and-matched.
//
// In addition, this example includes some fast automatic 'data smoothing' at
// lower noise speeds to help produce smoother animations in those cases.
//
// The FastLED built-in color palettes (Forest, Clouds, Lava, Ocean, Party) are
// used, as well as some 'hand-defined' ones, and some proceedurally generated
// palettes.


#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)

// The leds
CRGB leds[kMatrixWidth * kMatrixHeight];

// The 16 bit version of our coordinates
static uint16_t x;
static uint16_t y;
static uint16_t z;

// We're using the x/y dimensions to map to the x/y pixels on the matrix. We'll
// use the z-axis for "time". speed determines how fast time moves forward. Try
// 1 for a very slow moving effect, or 60 for something that ends up looking like
// water.
uint16_t speed = 20; // speed is set dynamically once we've started up

// Scale determines how far apart the pixels in our noise matrix are. Try
// changing these values around to see how it affects the motion of the display. The
// higher the value of scale, the more "zoomed out" the noise iwll be. A value
// of 1 will be so zoomed in, you'll mostly see solid colors.
uint16_t scale = 30; // scale is set dynamically once we've started up

// This is the array that we keep our computed noise values in
uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];

CRGBPalette16 currentPalette( PartyColors_p );
uint8_t colorLoop = 1;

void setup() {
delay(3000);
LEDS.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds,NUM_LEDS);
LEDS.setBrightness(BRIGHTNESS);

// Initialize our coordinates to some random values
x = random16();
y = random16();
z = random16();
}

// Fill the x/y array of 8-bit noise values using the inoise8 function.
void fillnoise8() {
// If we're runing at a low "speed", some 8-bit artifacts become visible
// from frame-to-frame. In order to reduce this, we can do some fast data-smoothing.
// The amount of data smoothing we're doing depends on "speed".
uint8_t dataSmoothing = 0;
if( speed < 50) {
dataSmoothing = 200 - (speed * 4);
}

for(int i = 0; i < MAX_DIMENSION; i++) {
int ioffset = scale * i;
for(int j = 0; j < MAX_DIMENSION; j++) {
int joffset = scale * j;

uint8_t data = inoise8(x + ioffset,y + joffset,z);

// The range of the inoise8 function is roughly 16-238.
// These two operations expand those values out to roughly 0..255
// You can comment them out if you want the raw noise data.
data = qsub8(data,16);
data = qadd8(data,scale8(data,39));

if( dataSmoothing ) {
uint8_t olddata = noise[i][j];
uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing);
data = newdata;
}

noise[i][j] = data;
}
}

z += speed;

// apply slow drift to X and Y, just for visual variation.
x += speed / 8;
y -= speed / 16;
}

void mapNoiseToLEDsUsingPalette()
{
static uint8_t ihue=0;

for(int i = 0; i < kMatrixWidth; i++) {
for(int j = 0; j < kMatrixHeight; j++) {
// We use the value at the (i,j) coordinate in the noise
// array for our brightness, and the flipped value from (j,i)
// for our pixel's index into the color palette.

uint8_t index = noise[j][i];
uint8_t bri = noise[i][j];

// if this palette is a 'loop', add a slowly-changing base value
if( colorLoop) {
index += ihue;
}

// brighten up, as the color palette itself often contains the
// light/dark dynamic range desired
if( bri > 127 ) {
bri = 255;
} else {
bri = dim8_raw( bri * 2);
}

CRGB color = ColorFromPalette( currentPalette, index, bri);
leds[XY(i,j)] = color;
}
}

ihue+=1;
}

void loop() {
// Periodically choose a new palette, speed, and scale
ChangePaletteAndSettingsPeriodically();

// generate noise data
fillnoise8();

// convert the noise data to colors in the LED array
// using the current palette
mapNoiseToLEDsUsingPalette();

LEDS.show();
// delay(10);
}

// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly.

// 1 = 5 sec per palette
// 2 = 10 sec per palette
// etc
#define HOLD_PALETTES_X_TIMES_AS_LONG 1

void ChangePaletteAndSettingsPeriodically()
{
uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60;
static uint8_t lastSecond = 99;

if( lastSecond != secondHand) {
lastSecond = secondHand;
if( secondHand == 0) { currentPalette = RainbowColors_p; speed = 20; scale = 30; colorLoop = 1; }
if( secondHand == 5) { SetupPurpleAndGreenPalette(); speed = 10; scale = 50; colorLoop = 1; }
if( secondHand == 10) { SetupBlackAndWhiteStripedPalette(); speed = 20; scale = 30; colorLoop = 1; }
if( secondHand == 15) { currentPalette = ForestColors_p; speed = 8; scale =120; colorLoop = 0; }
if( secondHand == 20) { currentPalette = CloudColors_p; speed = 4; scale = 30; colorLoop = 0; }
if( secondHand == 25) { currentPalette = LavaColors_p; speed = 8; scale = 50; colorLoop = 0; }
if( secondHand == 30) { currentPalette = OceanColors_p; speed = 20; scale = 90; colorLoop = 0; }
if( secondHand == 35) { currentPalette = PartyColors_p; speed = 20; scale = 30; colorLoop = 1; }
if( secondHand == 40) { SetupRandomPalette(); speed = 20; scale = 20; colorLoop = 1; }
if( secondHand == 45) { SetupRandomPalette(); speed = 50; scale = 50; colorLoop = 1; }
if( secondHand == 50) { SetupRandomPalette(); speed = 90; scale = 90; colorLoop = 1; }
if( secondHand == 55) { currentPalette = RainbowStripeColors_p; speed = 30; scale = 20; colorLoop = 1; }
}
}

// This function generates a random palette that's a gradient
// between four different colors. The first is a dim hue, the second is
// a bright hue, the third is a bright pastel, and the last is
// another bright hue. This gives some visual bright/dark variation
// which is more interesting than just a gradient of different hues.
void SetupRandomPalette()
{
currentPalette = CRGBPalette16(
CHSV( random8(), 255, 32),
CHSV( random8(), 255, 255),
CHSV( random8(), 128, 255),
CHSV( random8(), 255, 255));
}

// This function sets up a palette of black and white stripes,
// using code. Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
// 'black out' all 16 palette entries...
fill_solid( currentPalette, 16, CRGB::Black);
// and set every fourth one to white.
currentPalette[0] = CRGB::White;
currentPalette[4] = CRGB::White;
currentPalette[8] = CRGB::White;
currentPalette[12] = CRGB::White;

}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
CRGB purple = CHSV( HUE_PURPLE, 255, 255);
CRGB green = CHSV( HUE_GREEN, 255, 255);
CRGB black = CRGB::Black;

currentPalette = CRGBPalette16(
green, green, black, black,
purple, purple, black, black,
green, green, black, black,
purple, purple, black, black );
}


//
// Mark's xy coordinate mapping code. See the XYMatrix for more information on it.
//
uint16_t XY( uint8_t x, uint8_t y)
{
uint16_t i;
if( kMatrixSerpentineLayout == false) {
i = (y * kMatrixWidth) + x;
}
if( kMatrixSerpentineLayout == true) {
if( y & 0x01) {
// Odd rows run backwards
uint8_t reverseX = (kMatrixWidth - 1) - x;
i = (y * kMatrixWidth) + reverseX;
} else {
// Even rows run forwards
i = (y * kMatrixWidth) + x;
}
}
return i;
}

Here are a few things that we need to change from the actual example sketch.

#define LED_PIN     9
#define BRIGHTNESS 96
#define LED_TYPE WS2811
#define COLOR_ORDER GRB

const uint8_t kMatrixWidth = 16;
const uint8_t kMatrixHeight = 8;
const bool kMatrixSerpentineLayout = false;


We change the LED_PIN according to the Pin we have connected.

Brightness can be controlled as well up to 0-255.

kMatrixWidth and Height need to be changed as well according to our matrix layout which is 16x8.

This is important, kMatrixSerpentineLayout needs to be set to false, as our Matrix is not in Serpentine layout.


Step 11: What's Next

So here's what I'm gonna do next, I will prepare a big matrix at least 16x16 or even bigger, and use this software to project some video onto the matrix, the goal would be to see the video or image projecting on the matrix clearly by adding more pixels.

This is it for today folks, Leave a comment if you need any help regarding this project.

Thanks, Seeed Studio for supporting this project, you can check them out for getting all sorts of services or materials from their site.

Peace out!