Introduction: Custom 16x16 WS2812 Mini Matrix Project

About: Hello world;

Hey what's up you guys?

So here's another Matrix Project made by using an unusual RGB LED Part, WS2812B 3535 Package.

256 RGB LEDs are arranged in a 16x16 grid, WS2812 Addressable LEDs were used so no LED Driver or complex multiplexing circuit has been added to this board.

As for the Layout, all the LEDs are 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 can be controlled by both Adafruit Neopixel Library and FastLED Library or a similar matrix library.

Check out my previous Matrix Project 16x8- 

https://www.instructables.com/Custom-16x8-Ws2812-Matrix-Project/

This Instructables is about the whole built process of this matrix board so let's get started.

Supplies

Following are the materials required in this built-

  • WS2812B 3535 LEDs
  • Solder Paste
  • Arduino Nano Board
  • Custom PCB
  • Stencil
  • Jumper wires and breadboard

Step 1: WS2812B 3535 Package

WS2812B is an intelligent control LED light source that the control circuit and RGB chips, but they usually come in the 5050 Package.

There also exists a 2020 Package and a 3535 package

WS2812B 3535 that is being used in this project works the same as the 5050 Version, the only difference is the size and Flux of Light.

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.

Datasheet- https://www.elecrow.com/download/product/1RGWS2812B0MINI0/WS2812B-3535_RGB_LED_Chips_datesheet.pdf'

Step 2: PCB Design

We start first with the schematic that consists of 256 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.

Step 3:

After completing the PCB Design, I exported the Gerber data and send it to ELECROW for samples.

For this order, White Soldermask with black silkscreen was chosen, 1.5mm Board thickness with 1 oz copper.

Also, because this matrix has 256 LEDs, it was super hard if we manually add solder paste to each component pad which in this case would be 1024 as each led has four pads.

We ordered a stencil for applying solder paste and making the soldering process easy.

PCB Service from Elecrow was Super Awesome

ELecrow provides various professional services, including PCB manufacturing,PCB assembly, and 3D printing service. The PCB manufacturing price is as low as $1. Besides, the packaging was pretty decent, and it took a week for delivery which was super fast.

Check out Elecrow for getting great PCB services for less cost.

Step 4: PCB ASSEMBLY

  • Solder paste dispensing
  • Pick and place
  • Hotplate Reflow

Step 5: Solderpaste Dispensing With Stencil

We first apply solder paste to the board.

I usually use a dispensing syringe for this process, but this matrix contains 256 LEDs each with four pads, so 1024 pads require solderpaste.

Instead of manually applying solder paste to each component pad, we use STENCIL.

STENCIL

A stencil is a Metal Sheet with Cuts and slots made according to the soldermask layer of the PCB, goal of the stencil is to apply solderpaste on the PCB through the cuts and slots.

  • As for applying the stencil, we first place the main PCB in the same view as it was in the PCB Design.
  • We add three PCBs to hold the Main PCB in middle by sticking all the PCBs with tape so they won't move and keep Main PCB in its place.
  • Next, we add the fourth PCB and on top of the fourth PCB, we add a stencil.
  • we slide the stencil on top of the main PCB to match the pads of components and then add tape to hold the stencil in its place.
  • Next, we add solder paste to both sides of the stencil and then use a scraping tool to apply solder paste to each slot. solderpaste will fill the slots and get added to component pads.

Step 6: Pick and Place

Next, we gather 256 LEDs and get ready for the Pick and place process.

  • To get this step right the first time, we arrange LEDs in a grid of 16x16.
  • We pick each LED by using an ESD Tweezer and place them on their pad in the right alignment.
  • We do this process for 256 LEDs and then check their alignment before moving to the next step which is the Hotplate Reflow Process.

Step 7: Hotplate Reflow Process

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 8: Arduino Setup

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 D3 (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 9: Libraries Required

For using this board, we can utilize a bunch of existing Libraries but we will focus on FASTLED LIBRARY and Adafruit's Neopixel 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 Adafruit's Neopixel Library GitHub page- https://github.com/adafruit/Adafruit_NeoPixel

Step 10: LED TESTING

For the first test, we will be utilizing Adafruit Neopixel Library, we open Adafruit Neopixel example sketches and upload a sketch called "simple" into the Arduino Board.

Here's the sketch-

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1
#define PIN 3

// How many NeoPixels are attached to the Arduino?
#define NUMPIXELS 240

// When we setup 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 = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int delayval = 100; // delay for half a second

void setup() {
// This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket
#if defined (__AVR_ATtiny85__)
if (F_CPU == 16000000) clock_prescale_set(clock_div_1);
#endif
// End of trinket special code

pixels.begin(); // This initializes the NeoPixel library.
}

void loop() {

// For a set of NeoPixels the first NeoPixel is 0, second is 1, all the way up to the count of pixels minus one.

for(int i=0;i<NUMPIXELS;i++){

// pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
pixels.setPixelColor(i, pixels.Color(0,150,0)); // Moderately bright green color.

pixels.show(); // This sends the updated pixel color to the hardware.

delay(delayval); // Delay for a period of time (in milliseconds).

}
}

After Uploading, LEDs light up one by one in a sequence, this is like an LED counter Sketch that counts each led one by one.

Step 11: CYCLON

Next, we use the FastLED library and open the cyclone sketch from its example menu.

Here's the sketch-

#include <FastLED.h>

// How many leds in your strip?
#define NUM_LEDS 240

// For led chips like Neopixels, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806, define both DATA_PIN and CLOCK_PIN
#define DATA_PIN 3
#define CLOCK_PIN 13

// Define the array of leds
CRGB leds[NUM_LEDS];

void setup() {
Serial.begin(57600);
Serial.println("resetting");
LEDS.addLeds<WS2812,DATA_PIN,RGB>(leds,NUM_LEDS);
LEDS.setBrightness(84);
}

void fadeall() { for(int i = 0; i < NUM_LEDS; i++) { leds[i].nscale8(250); } }

void loop() {
static uint8_t hue = 0;
Serial.print("x");
// First slide the led in one direction
for(int i = 0; i < NUM_LEDS; i++) {
// Set the i'th led to red
leds[i] = CHSV(hue++, 255, 255);
// Show the leds
FastLED.show();
// now that we've shown the leds, reset the i'th led to black
// leds[i] = CRGB::Black;
fadeall();
// Wait a little bit before we loop around and do it again
delay(10);
}
Serial.print("x");

// Now go in the other direction.
for(int i = (NUM_LEDS)-1; i >= 0; i--) {
// Set the i'th led to red
leds[i] = CHSV(hue++, 255, 255);
// Show the leds
FastLED.show();
// now that we've shown the leds, reset the i'th led to black
// leds[i] = CRGB::Black;
fadeall();
// Wait a little bit before we loop around and do it again
delay(10);
}
}

Step 12: NOISE PLAYGROUND

Next is the Noise Playground Sketch, it lights up LEDs in an interesting way which makes an amazing to look pattern, it almost looks like some kind of bacteria maze or liquid simulation.

#include <FastLED.h>

#define kMatrixWidth 16
#define kMatrixHeight 15

#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
// Param for different pixel layouts
#define kMatrixSerpentineLayout false

// led array
CRGB leds[kMatrixWidth * kMatrixHeight];

// x,y, & time values
uint32_t x,y,v_time,hue_time,hxy;

// Play with the values of the variables below and see what kinds of effects they
// have! More octaves will make things slower.

// how many octaves to use for the brightness and hue functions
uint8_t octaves=1;
uint8_t hue_octaves=3;

// the 'distance' between points on the x and y axis
int xscale=57771;
int yscale=57771;

// the 'distance' between x/y points for the hue noise
int hue_scale=1;

// how fast we move through time & hue noise
int time_speed=1111;
int hue_speed=31;

// adjust these values to move along the x or y axis between frames
int x_speed=331;
int y_speed=1111;

void loop() {
// fill the led array 2/16-bit noise values
fill_2dnoise16(LEDS.leds(), kMatrixWidth, kMatrixHeight, kMatrixSerpentineLayout,
octaves,x,xscale,y,yscale,v_time,
hue_octaves,hxy,hue_scale,hxy,hue_scale,hue_time, false);

LEDS.show();

// adjust the intra-frame time values
x += x_speed;
y += y_speed;
v_time += time_speed;
hue_time += hue_speed;
// delay(50);
}


void setup() {
// initialize the x/y and time values
random16_set_seed(8934);
random16_add_entropy(analogRead(3));

Serial.begin(57600);
Serial.println("resetting!");

delay(3000);
LEDS.addLeds<WS2811,3,GRB>(leds,NUM_LEDS);
LEDS.setBrightness(96);

hxy = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
x = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
y = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
v_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();
hue_time = (uint32_t)((uint32_t)random16() << 16) + (uint32_t)random16();

}

Step 13: XY MATRIX

Next is the XY Matrix sketch that lights up leds in a moving way, it looks like LED segments are going from TOP to BOTTOM.

#include <FastLED.h>

#define LED_PIN 3

#define COLOR_ORDER GRB
#define CHIPSET WS2811

#define BRIGHTNESS 64

// Helper functions for an two-dimensional XY matrix of pixels.
// Simple 2-D demo code is included as well.
//
// XY(x,y) takes x and y coordinates and returns an LED index number,
// for use like this: leds[ XY(x,y) ] == CRGB::Red;
// No error checking is performed on the ranges of x and y.
//
// XYsafe(x,y) takes x and y coordinates and returns an LED index number,
// for use like this: leds[ XY(x,y) ] == CRGB::Red;
// Error checking IS performed on the ranges of x and y, and an
// index of "-1" is returned. Special instructions below
// explain how to use this without having to do your own error
// checking every time you use this function.
// This is a slightly more advanced technique, and
// it REQUIRES SPECIAL ADDITIONAL setup, described below.


// Params for width and height
const uint8_t kMatrixWidth = 16;
const uint8_t kMatrixHeight = 15;

// Param for different pixel layouts
const bool kMatrixSerpentineLayout = false;
const bool kMatrixVertical = false;
// Set 'kMatrixSerpentineLayout' to false if your pixels are
// laid out all running the same way, like this:
//
// 0 > 1 > 2 > 3 > 4
// |
// .----<----<----<----'
// |
// 5 > 6 > 7 > 8 > 9
// |
// .----<----<----<----'
// |
// 10 > 11 > 12 > 13 > 14
// |
// .----<----<----<----'
// |
// 15 > 16 > 17 > 18 > 19
//
// Set 'kMatrixSerpentineLayout' to true if your pixels are
// laid out back-and-forth, like this:
//
// 0 > 1 > 2 > 3 > 4
// |
// |
// 9 < 8 < 7 < 6 < 5
// |
// |
// 10 > 11 > 12 > 13 > 14
// |
// |
// 19 < 18 < 17 < 16 < 15
//
// Bonus vocabulary word: anything that goes one way
// in one row, and then backwards in the next row, and so on
// is call "boustrophedon", meaning "as the ox plows."


// This function will return the right 'led index number' for
// a given set of X and Y coordinates on your matrix.
// IT DOES NOT CHECK THE COORDINATE BOUNDARIES.
// That's up to you. Don't pass it bogus values.
//
// Use the "XY" function like this:
//
// for( uint8_t x = 0; x < kMatrixWidth; x++) {
// for( uint8_t y = 0; y < kMatrixHeight; y++) {
//
// // Here's the x, y to 'led index' in action:
// leds[ XY( x, y) ] = CHSV( random8(), 255, 255);
//
// }
// }
//
//
uint16_t XY( uint8_t x, uint8_t y)
{
uint16_t i;

if( kMatrixSerpentineLayout == false) {
if (kMatrixVertical == false) {
i = (y * kMatrixWidth) + x;
} else {
i = kMatrixHeight * (kMatrixWidth - (x+1))+y;
}
}

if( kMatrixSerpentineLayout == true) {
if (kMatrixVertical == false) {
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;
}
} else { // vertical positioning
if ( x & 0x01) {
i = kMatrixHeight * (kMatrixWidth - (x+1))+y;
} else {
i = kMatrixHeight * (kMatrixWidth - x) - (y+1);
}
}
}

return i;
}


// Once you've gotten the basics working (AND NOT UNTIL THEN!)
// here's a helpful technique that can be tricky to set up, but
// then helps you avoid the needs for sprinkling array-bound-checking
// throughout your code.
//
// It requires a careful attention to get it set up correctly, but
// can potentially make your code smaller and faster.
//
// Suppose you have an 8 x 5 matrix of 40 LEDs. Normally, you'd
// delcare your leds array like this:
// CRGB leds[40];
// But instead of that, declare an LED buffer with one extra pixel in
// it, "leds_plus_safety_pixel". Then declare "leds" as a pointer to
// that array, but starting with the 2nd element (id=1) of that array:
// CRGB leds_with_safety_pixel[41];
// CRGB* const leds( leds_plus_safety_pixel + 1);
// Then you use the "leds" array as you normally would.
// Now "leds[0..N]" are aliases for "leds_plus_safety_pixel[1..(N+1)]",
// AND leds[-1] is now a legitimate and safe alias for leds_plus_safety_pixel[0].
// leds_plus_safety_pixel[0] aka leds[-1] is now your "safety pixel".
//
// Now instead of using the XY function above, use the one below, "XYsafe".
//
// If the X and Y values are 'in bounds', this function will return an index
// into the visible led array, same as "XY" does.
// HOWEVER -- and this is the trick -- if the X or Y values
// are out of bounds, this function will return an index of -1.
// And since leds[-1] is actually just an alias for leds_plus_safety_pixel[0],
// it's a totally safe and legal place to access. And since the 'safety pixel'
// falls 'outside' the visible part of the LED array, anything you write
// there is hidden from view automatically.
// Thus, this line of code is totally safe, regardless of the actual size of
// your matrix:
// leds[ XYsafe( random8(), random8() ) ] = CHSV( random8(), 255, 255);
//
// The only catch here is that while this makes it safe to read from and
// write to 'any pixel', there's really only ONE 'safety pixel'. No matter
// what out-of-bounds coordinates you write to, you'll really be writing to
// that one safety pixel. And if you try to READ from the safety pixel,
// you'll read whatever was written there last, reglardless of what coordinates
// were supplied.

#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
CRGB leds_plus_safety_pixel[ NUM_LEDS + 1];
CRGB* const leds( leds_plus_safety_pixel + 1);

uint16_t XYsafe( uint8_t x, uint8_t y)
{
if( x >= kMatrixWidth) return -1;
if( y >= kMatrixHeight) return -1;
return XY(x,y);
}


// Demo that USES "XY" follows code below

void loop()
{
uint32_t ms = millis();
int32_t yHueDelta32 = ((int32_t)cos16( ms * (27/1) ) * (350 / kMatrixWidth));
int32_t xHueDelta32 = ((int32_t)cos16( ms * (39/1) ) * (310 / kMatrixHeight));
DrawOneFrame( ms / 65536, yHueDelta32 / 32768, xHueDelta32 / 32768);
if( ms < 5000 ) {
FastLED.setBrightness( scale8( BRIGHTNESS, (ms * 256) / 5000));
} else {
FastLED.setBrightness(BRIGHTNESS);
}
FastLED.show();
}

void DrawOneFrame( byte startHue8, int8_t yHueDelta8, int8_t xHueDelta8)
{
byte lineStartHue = startHue8;
for( byte y = 0; y < kMatrixHeight; y++) {
lineStartHue += yHueDelta8;
byte pixelHue = lineStartHue;
for( byte x = 0; x < kMatrixWidth; x++) {
pixelHue += xHueDelta8;
leds[ XY(x, y)] = CHSV( pixelHue, 255, 255);
}
}
}


void setup() {
FastLED.addLeds<CHIPSET, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalSMD5050);
FastLED.setBrightness( BRIGHTNESS );
}

Step 14: PACIFICA

Now, this is something special, PACIFICA sketch imitates an Ocean wave or water stream, it turns on and off LEDs in such a way that some area looks deep blue in color and some white which looks like moving water steam.

//
// "Pacifica"
// Gentle, blue-green ocean waves.
// December 2019, Mark Kriegsman and Mary Corey March.
// For Dan.
//

#define FASTLED_ALLOW_INTERRUPTS 0
#include <FastLED.h>
FASTLED_USING_NAMESPACE

#define DATA_PIN 3
#define NUM_LEDS 240
#define MAX_POWER_MILLIAMPS 500
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB

//////////////////////////////////////////////////////////////////////////

CRGB leds[NUM_LEDS];

void setup() {
delay( 3000); // 3 second delay for boot recovery, and a moment of silence
FastLED.addLeds<LED_TYPE,DATA_PIN,COLOR_ORDER>(leds, NUM_LEDS)
.setCorrection( TypicalLEDStrip );
FastLED.setMaxPowerInVoltsAndMilliamps( 5, MAX_POWER_MILLIAMPS);
}

void loop()
{
EVERY_N_MILLISECONDS( 20) {
pacifica_loop();
FastLED.show();
}
}

//////////////////////////////////////////////////////////////////////////
//
// The code for this animation is more complicated than other examples, and
// while it is "ready to run", and documented in general, it is probably not
// the best starting point for learning. Nevertheless, it does illustrate some
// useful techniques.
//
//////////////////////////////////////////////////////////////////////////
//
// In this animation, there are four "layers" of waves of light.
//
// Each layer moves independently, and each is scaled separately.
//
// All four wave layers are added together on top of each other, and then
// another filter is applied that adds "whitecaps" of brightness where the
// waves line up with each other more. Finally, another pass is taken
// over the led array to 'deepen' (dim) the blues and greens.
//
// The speed and scale and motion each layer varies slowly within independent
// hand-chosen ranges, which is why the code has a lot of low-speed 'beatsin8' functions
// with a lot of oddly specific numeric ranges.
//
// These three custom blue-green color palettes were inspired by the colors found in
// the waters off the southern coast of California, https://goo.gl/maps/QQgd97jjHesHZVxQ7
//
CRGBPalette16 pacifica_palette_1 =
{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50 };
CRGBPalette16 pacifica_palette_2 =
{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F };
CRGBPalette16 pacifica_palette_3 =
{ 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33,
0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF };


void pacifica_loop()
{
// Increment the four "color index start" counters, one for each wave layer.
// Each is incremented at a different speed, and the speeds vary over time.
static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4;
static uint32_t sLastms = 0;
uint32_t ms = GET_MILLIS();
uint32_t deltams = ms - sLastms;
sLastms = ms;
uint16_t speedfactor1 = beatsin16(3, 179, 269);
uint16_t speedfactor2 = beatsin16(4, 179, 269);
uint32_t deltams1 = (deltams * speedfactor1) / 256;
uint32_t deltams2 = (deltams * speedfactor2) / 256;
uint32_t deltams21 = (deltams1 + deltams2) / 2;
sCIStart1 += (deltams1 * beatsin88(1011,10,13));
sCIStart2 -= (deltams21 * beatsin88(777,8,11));
sCIStart3 -= (deltams1 * beatsin88(501,5,7));
sCIStart4 -= (deltams2 * beatsin88(257,4,6));

// Clear out the LED array to a dim background blue-green
fill_solid( leds, NUM_LEDS, CRGB( 2, 6, 10));

// Render each of four layers, with different scales and speeds, that vary over time
pacifica_one_layer( pacifica_palette_1, sCIStart1, beatsin16( 3, 11 * 256, 14 * 256), beatsin8( 10, 70, 130), 0-beat16( 301) );
pacifica_one_layer( pacifica_palette_2, sCIStart2, beatsin16( 4, 6 * 256, 9 * 256), beatsin8( 17, 40, 80), beat16( 401) );
pacifica_one_layer( pacifica_palette_3, sCIStart3, 6 * 256, beatsin8( 9, 10,38), 0-beat16(503));
pacifica_one_layer( pacifica_palette_3, sCIStart4, 5 * 256, beatsin8( 8, 10,28), beat16(601));

// Add brighter 'whitecaps' where the waves lines up more
pacifica_add_whitecaps();

// Deepen the blues and greens a bit
pacifica_deepen_colors();
}

// Add one layer of waves into the led array
void pacifica_one_layer( CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff)
{
uint16_t ci = cistart;
uint16_t waveangle = ioff;
uint16_t wavescale_half = (wavescale / 2) + 20;
for( uint16_t i = 0; i < NUM_LEDS; i++) {
waveangle += 250;
uint16_t s16 = sin16( waveangle ) + 32768;
uint16_t cs = scale16( s16 , wavescale_half ) + wavescale_half;
ci += cs;
uint16_t sindex16 = sin16( ci) + 32768;
uint8_t sindex8 = scale16( sindex16, 240);
CRGB c = ColorFromPalette( p, sindex8, bri, LINEARBLEND);
leds[i] += c;
}
}

// Add extra 'white' to areas where the four layers of light have lined up brightly
void pacifica_add_whitecaps()
{
uint8_t basethreshold = beatsin8( 9, 55, 65);
uint8_t wave = beat8( 7 );

for( uint16_t i = 0; i < NUM_LEDS; i++) {
uint8_t threshold = scale8( sin8( wave), 20) + basethreshold;
wave += 7;
uint8_t l = leds[i].getAverageLight();
if( l > threshold) {
uint8_t overage = l - threshold;
uint8_t overage2 = qadd8( overage, overage);
leds[i] += CRGB( overage, overage2, qadd8( overage2, overage2));
}
}
}

// Deepen the blues and greens
void pacifica_deepen_colors()
{
for( uint16_t i = 0; i < NUM_LEDS; i++) {
leds[i].blue = scale8( leds[i].blue, 145);
leds[i].green= scale8( leds[i].green, 200);
leds[i] |= CRGB( 2, 5, 7);
}
}



Step 15: Conclusion, What's Next?

WS2812 LEDs are easy to work with and are super great for projects like this.

The next phase of this project is to prepare a display with this Matrix that will run some sort of animation by using 16x16 pixels.

this is for the next project so stay tuned for that, if you have any questions regarding this project, leave a comment.

Special thanks to ELECROW for supporting this project, do check them out

Peace.