Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
John Bradnam
Published © GPL3+

Hot Glue LED Matrix Lamp

An interesting light display using 64 glue sticks and 128 WS2812B RGB LED strips.

IntermediateFull instructions provided2 days29,700
Hot Glue LED Matrix Lamp

Things used in this project

Hardware components

WS2812 Addressable LED Strip
Digilent WS2812 Addressable LED Strip
IP60 (60 LEDs per metre, Non-waterproof, total 128 LEDs)
×128
Arduino Nano R3
Arduino Nano R3
×1
3A Mini DC-DC step down converter volt regulator
×1
Resistor 10k ohm
Resistor 10k ohm
×1
Resistor 330 ohm
Resistor 330 ohm
×1
Capacitor 470 µF
Capacitor 470 µF
10V Low profile type
×1
10K lin 45mm Slider Potentometer (30mm stroke length eg: Bourns PTA3043)
×1
Small push button
×1
Small Rocker Switch
×1
DC power socket (panel version)
×1
7mm x 100mm Glue Stick
×64

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

STL Files

STL files for 3D printing

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

GlueMatrixTest.ino

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

#include "Button.h"

#define PIN_LED 2
#define PIN_SWITCH 3
#define PIN_POT A0

#define LEDS 128

// Parameter 1 = number of pixels in strip
// Parameter 2 = Arduino pin number (most are valid)
// Parameter 3 = pixel type flags, add together as needed:
//   NEO_KHZ800  800 KHz bitstream (most NeoPixel products w/WS2812 LEDs)
//   NEO_KHZ400  400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers)
//   NEO_GRB     Pixels are wired for GRB bitstream (most NeoPixel products)
//   NEO_RGB     Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2)
//   NEO_RGBW    Pixels are wired for RGBW bitstream (NeoPixel RGBW products)
Adafruit_NeoPixel strip = Adafruit_NeoPixel(LEDS, PIN_LED, NEO_GRB + NEO_KHZ800);
Button mode = Button(PIN_SWITCH);
bool modePressed = false;

//Physical LED map for bottom and top LED arrays

//LED Bottom (Back looking from top)
//064 049 048 033 032 017 016 001
//063 050 047 034 031 018 015 002
//062 051 046 035 030 019 014 003
//061 052 045 036 029 020 013 004
//060 053 044 037 028 021 012 005
//059 054 043 038 027 022 011 006
//058 055 042 039 026 023 010 007
//057 056 041 040 025 024 009 008
//(Front looking from top)

//LED Top (Back looking from top)
//065 080 081 096 097 112 113 128
//066 079 082 095 098 111 114 127
//067 078 083 094 099 110 115 126
//068 077 084 093 100 109 116 125
//069 076 085 092 101 108 117 124
//070 075 086 091 102 107 118 123
//071 074 087 090 103 106 119 122
//072 073 088 089 104 105 120 121
//(Front looking from top)

const uint8_t botLED[] PROGMEM = {
  64,49,48,33,32,17,16,1,
  63,50,47,34,31,18,15,2,
  62,51,46,35,30,19,14,3,
  61,52,45,36,29,20,13,4,
  60,53,44,37,28,21,12,5,
  59,54,43,38,27,22,11,6,
  58,55,42,39,26,23,10,7,
  57,56,41,40,25,24,9,8,
};

const uint8_t topLED[] PROGMEM = {
  65,80,81,96,97,112,113,128,
  66,79,82,95,98,111,114,127,
  67,78,83,94,99,110,115,126,
  68,77,84,93,100,109,116,125,
  69,76,85,92,101,108,117,124,
  70,75,86,91,102,107,118,123,
  71,74,87,90,103,106,119,122,
  72,73,88,89,104,105,120,121
};

//Storage for current values
int red = 128;
int green = 128;
int blue = 128;
int pattern = 1;

// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across
// pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input
// and minimize distance between Arduino and first pixel.  Avoid connecting
// on a live circuit...if you must, connect GND first.

void setup() 
{
  Serial.begin(115200);

  pinMode(PIN_LED, OUTPUT);
  pinMode(PIN_SWITCH, INPUT);
  pinMode(PIN_POT, INPUT);
  
  //Pixel Strip
  Serial.println("Setup()");
  strip.begin();
  strip.show(); // Initialize all pixels to 'off'

  //Button callbacks
  //mode.Background(ButtonBackground);

  //Set ISR for pin change on MODE pin
  Button::PinChangeSetup(PIN_SWITCH);
}

void loop() 
{
  if (modePressed)
  {
    pattern = (pattern % 8) + 1; 
    strip.clear();
  }
  modePressed = false;
  Serial.print("Mode ");
  Serial.print(pattern, DEC);
  Serial.println();
	switch (pattern)
	{
	  case 1: colorWipe(strip.Color(255, 0, 0)); break; // Red
	  case 2: colorWipe(strip.Color(0, 255, 0)); break; // Green
	  case 3: colorWipe(strip.Color(0, 0, 255)); break; // Blue
	  case 4: theaterChase(strip.Color(127, 127, 127)); break; // White
	  case 5: rainbow(); break;
    case 6: rainbowDifference(); break;
	  case 7: rainbowCycle(); break;
    case 8: rainbowCycleDifference(); break;
	  case 9: theaterChaseRainbow(); break;
	}
	if (!modePressed)
	{
    modePressed = mode.Pressed();
	}
}

//Mode button interrupt to break out of loops etc
//PCINT1 handles pin changes for pins for A0 to A5
ISR (PCINT2_vect)
{
  modePressed = modePressed | (mode.State() == LOW);
}

void ButtonBackground(void)
{
}

// Fill the dots one after the other with a color
void colorWipe(uint32_t c) 
{
  int total = strip.numPixels() / 2;
  for(uint16_t i=0; i < total && !modePressed; i++) 
  {
    uint8_t botIndex = pgm_read_byte(&botLED[i]) - 1;
    strip.setPixelColor(botIndex, c);
    uint8_t topIndex = pgm_read_byte(&topLED[i]) - 1;
    strip.setPixelColor(topIndex, c);
    strip.show();
    delay(map(analogRead(PIN_POT), 0, 1024, 100, 0));
  }
  for(uint16_t i=total; i > 0 && !modePressed; i--) 
  {
    uint8_t botIndex = pgm_read_byte(&botLED[i]) - 1;
    strip.setPixelColor(botIndex, 0);
    uint8_t topIndex = pgm_read_byte(&topLED[i]) - 1;
    strip.setPixelColor(topIndex, 0);
    strip.show();
    delay(map(analogRead(PIN_POT), 0, 1024, 100, 0));
  }
}

void rainbow() 
{
  int total = strip.numPixels() / 2;
  for(uint16_t j=0; j < 256 && !modePressed; j++) 
  {
    for(uint16_t i=0; i < total && !modePressed; i++) 
	  {
      uint32_t c = Wheel((i+j) & 255);
      uint8_t botIndex = pgm_read_byte(&botLED[i]) - 1;
      strip.setPixelColor(botIndex, c);
      uint8_t topIndex = pgm_read_byte(&topLED[i]) - 1;
      strip.setPixelColor(topIndex, c);
    }
    strip.show();
    delay(map(analogRead(PIN_POT), 0, 1024, 40, 0));
  }
}

void rainbowDifference() 
{
  int total = strip.numPixels() / 2;
  for(uint16_t j=0; j < 256 && !modePressed; j++) 
  {
    for(uint16_t i=0; i < total && !modePressed; i++) 
    {
      uint32_t c = Wheel((i+j) & 255);
      uint8_t botIndex = pgm_read_byte(&botLED[i]) - 1;
      strip.setPixelColor(botIndex, c);
      c = Wheel((i+j+64) & 255);
      uint8_t topIndex = pgm_read_byte(&topLED[i]) - 1;
      strip.setPixelColor(topIndex, c);
    }
    strip.show();
    delay(map(analogRead(PIN_POT), 0, 1024, 40, 0));
  }
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycle() 
{
  int total = strip.numPixels() / 2;
  for(uint16_t j=0; j < 256*5 && !modePressed; j++) 
  { // 5 cycles of all colors on wheel
    for(uint16_t i=0; i < total && !modePressed; i++) 
	  {
      uint32_t c = Wheel(((i * 256 / total) + j) & 255);
      uint8_t botIndex = pgm_read_byte(&botLED[i]) - 1;
      strip.setPixelColor(botIndex, c);
      uint8_t topIndex = pgm_read_byte(&topLED[i]) - 1;
      strip.setPixelColor(topIndex, c);
    }
    strip.show();
    delay(map(analogRead(PIN_POT), 0, 1024, 40, 0));
  }
}

// Slightly different, this makes the rainbow equally distributed throughout
void rainbowCycleDifference() 
{
  int total = strip.numPixels() / 2;
  for(uint16_t j=0; j < 256*5 && !modePressed; j++) 
  { // 5 cycles of all colors on wheel
    for(uint16_t i=0; i < total && !modePressed; i++) 
    {
      uint32_t c = Wheel(((i * 256 / total) + j) & 255);
      uint8_t botIndex = pgm_read_byte(&botLED[i]) - 1;
      strip.setPixelColor(botIndex, c);
      c = Wheel(((i * 256 / total) + j + 64) & 255);
      uint8_t topIndex = pgm_read_byte(&topLED[i]) - 1;
      strip.setPixelColor(topIndex, c);
    }
    strip.show();
    delay(map(analogRead(PIN_POT), 0, 1024, 40, 0));
  }
}

//Theatre-style crawling lights.
void theaterChase(uint32_t c) 
{
  int total = strip.numPixels() / 2;
  for (int j=0; j < 10 && !modePressed; j++) 
  {  //do 10 cycles of chasing
    for (int q=0; q < 3 && !modePressed; q++) 
	  {
      for (uint16_t i=0; i < total && !modePressed; i=i+3) 
	    {
        uint8_t botIndex = pgm_read_byte(&botLED[i+q]) - 1; //turn every third pixel on
        strip.setPixelColor(botIndex, c);
        uint8_t topIndex = pgm_read_byte(&topLED[i+q]) - 1;
        strip.setPixelColor(topIndex, c);
      }
      strip.show();

      delay(map(analogRead(PIN_POT), 0, 1024, 1, 150));

      for (uint16_t i=0; i < total && !modePressed; i=i+3) 
	    {
        uint8_t botIndex = pgm_read_byte(&botLED[i+q]) - 1; ////turn every third pixel off
        strip.setPixelColor(botIndex, 0);
        uint8_t topIndex = pgm_read_byte(&topLED[i+q]) - 1;
        strip.setPixelColor(topIndex, 0);
      }
    }
  }
}

//Theatre-style crawling lights with rainbow effect
void theaterChaseRainbow() 
{
  int total = strip.numPixels() / 2;
  for (int j=0; j < 256 && !modePressed; j++) 
  {     // cycle all 256 colors in the wheel
    for (int q=0; q < 3 && !modePressed; q++) 
    {
      for (uint16_t i=0; i < total && !modePressed; i=i+3) 
	    {
        uint32_t c = Wheel( (i+j) % 255);
        uint8_t botIndex = pgm_read_byte(&botLED[i+q]) - 1; //turn every third pixel on
        strip.setPixelColor(botIndex, c);
        uint8_t topIndex = pgm_read_byte(&topLED[i+q]) - 1;
        strip.setPixelColor(topIndex, c);
      }
      strip.show();

      delay(map(analogRead(PIN_POT), 0, 1024, 1, 150));

      for (uint16_t i=0; i < total && !modePressed; i=i+3) 
	    {
        uint8_t botIndex = pgm_read_byte(&botLED[i+q]) - 1; ////turn every third pixel off
        strip.setPixelColor(botIndex, 0);
        uint8_t topIndex = pgm_read_byte(&topLED[i+q]) - 1;
        strip.setPixelColor(topIndex, 0);
      }
    }
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) 
{
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) 
  {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if(WheelPos < 170) 
  {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

Button.h

C Header File
/*
Class: Button
Author: John Bradnam (jbrad2089@gmail.com)
Purpose: Arduino library to handle buttons
*/
#pragma once
#include "Arduino.h"

#define DEBOUNCE_DELAY 5

//Repeat speed
#define REPEAT_START_SPEED 500
#define REPEAT_INCREASE_SPEED 50
#define REPEAT_MAX_SPEED 50

class Button
{
	public:
		//Simple constructor
		Button(int Pin);
    //Background function called when in a wait or repeat loop
    void Background(void (*pBackgroundFunction)());
		//Repeat function called when button is pressed
    void Repeat(void (*pRepeatFunction)());
		//Test whether button is pressed and released
		//Will call repeat function if one is provided
		bool Pressed();
		//Return button state (HIGH or LOW) - LOW = Pressed
		int State();
    //Pin Change Interrupt Setup
    //ISR (PCINT0_vect) pin change interrupt for D8 to D13 
    //ISR (PCINT1_vect) pin change interrupt for A0 to A5 
    //ISR (PCINT2_vect) pin change interrupt for D0 to D7
    static void PinChangeSetup(byte pin);

	private:
		int _pin;
		void (*_repeatCallback)(void);
    void (*_backgroundCallback)(void);

};

Button.cpp

C/C++
#include "Button.h"

Button::Button(int pin)
{
	_pin = pin;
	pinMode(_pin, INPUT);
}

//Set function to invoke in a delay or repeat loop
void Button::Background(void (*pBackgroundFunction)())
{
  _backgroundCallback = pBackgroundFunction;
}

//Set function to invoke if repeat system required
void Button::Repeat(void (*pRepeatFunction)())
{
  _repeatCallback = pRepeatFunction;
}

static void Button::PinChangeSetup(byte pin) 
{
  *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
  PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
  PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group 
}


//Tests if a button is pressed and released
//  returns true if the button was pressed and released
//	if repeat callback supplied, the callback is called while the key is pressed
bool Button::Pressed()
{
  bool pressed = false;
  if (digitalRead(_pin) == LOW)
  {
    unsigned long wait = millis() + DEBOUNCE_DELAY;
    while (millis() < wait)
    {
      if (_backgroundCallback != NULL)
      {
        _backgroundCallback();
      }
    }
    if (digitalRead(_pin) == LOW)
    {
  	  //Set up for repeat loop
  	  if (_repeatCallback != NULL)
  	  {
  	    _repeatCallback();
  	  }
  	  unsigned long speed = REPEAT_START_SPEED;
  	  unsigned long time = millis() + speed;
      while (digitalRead(_pin) == LOW)
      {
        if (_backgroundCallback != NULL)
        {
          _backgroundCallback();
        }
    		if (_repeatCallback != NULL && millis() >= time)
    		{
    		  _repeatCallback();
    		  unsigned long faster = speed - REPEAT_INCREASE_SPEED;
    		  if (faster >= REPEAT_MAX_SPEED)
    		  {
    			  speed = faster;
    		  }
    		  time = millis() + speed;
    		}
      }
      pressed = true;
    }
  }
  return pressed;
}

//Return current button state
int Button::State()
{
	return digitalRead(_pin);
}

Credits

John Bradnam
147 projects • 181 followers
Thanks to jbumstead.

Comments