Graduation Hat Decoration With Custom LED Matrix

by songdar in Circuits > LEDs

49 Views, 0 Favorites, 0 Comments

Graduation Hat Decoration With Custom LED Matrix

KakaoTalk_20251231_223712669_03.jpg

Graduation day is coming up in few months and many students decorate their graduation hats creatively. For myself, I came up with an idea to make a LED matrix (7 LEDs on each side) to attach on top of the hat that would display patterns and texts. Without any further delay, let's get building!

Supplies

ff.jpg
  1. 2m WS2813 LED Light Strip (I needed 49 LEDs, so I chose 30 LEDs/m) (or any other desired length)
  2. 1 X IR Remote Control Kit (remote controller & receiver)
  3. 1 X ESP32
  4. 1 X Boost Converter (3.7V to 5V)
  5. 1 X 3.7V 3300mAh 18650 Li-Ion Battery (make sure the battery can power the LED strip sufficiently)
  6. 1 X 18650 Battery Case
  7. Wires & Soldering Equipment

Schematic

graduation_hat.png

The circuit is pretty simple as the core features are: receive signal from IR remote control, then display image on the LED matrix accordingly.

I chose 3300mAh battery because the 49 LEDs on WS2813 will draw about 1.6A of current when it is at full brightness (white) and I want to make it run for a couple of hours. We have a boost converter to increase the voltage from 3.7V li-ion battery to 5V, which will power the ESP32 and WS2813.

The IR receiver will be powered by the ESP32 and its data pin is connected to D15.

WS2813 has 4 pins: VCC, GND, DO (D18) and BO (D19). DO pin stands for "Data Output" and it is the primary pin that outputs the data to LEDs. BO is a backup pin which allows WS2813 to run even if one LED is broken (if two or more LEDs are broken, then the rest of the LED strip would not work).

Soldering

sf.jpg

The graduation hat is 24cm by 24cm, so to solder seven strips that are 2.8cm apart from each other, I prepared the wires as the following:

Red Wire (5V): 3 of 8cm & 3 of 5cm

Yellow Wire (Data): 3 of 7cm & 3 of 6cm

Blue Wire (Backup): 3 of 6cm & 3 of 7cm

Black Wire (GND): 3 of 5cm & 3 of 8cm

When soldering, make sure the direction of current matches with the arrows indicated on WS2813.

You can solder IR receiver directly on to ESP32, but I used jumper wires.

IR Code Mapping

image832.png
#include "Freenove_IR_Lib_for_ESP32.h"

Freenove_ESP32_IR_Recv ir_recv(15); //IR Receiver Pin

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

void loop() {
ir_recv.task();
if(ir_recv.nec_available())
{
Serial.println(ir_recv.data());
}
}

Upload the following code and change the pin number on the second line to the pin you are using. If you check the serial monitor after clicking a button on the IR remote, you will see a 32-bit code that is designated to each button. Each IR remote has its own codes, so take note of the codes to use later in the project, where each button will have its own function.

Main Code

#include <Arduino.h>
#include <FastLED.h> //Libraries
#include "Freenove_IR_Lib_for_ESP32.h"
Freenove_ESP32_IR_Recv ir_recv(15); //Change it according to the pin number you are using for IR receiver

#define NUM_LEDS 49 //Number of LEDs
#define DATA_PIN 18 //Data Pin
#define BACKUP_PIN 19 //Backup Data Pin
CRGB leds[NUM_LEDS];
int brightness = 125; //Initial Brightness

// RGB Setting
//The arrays are in following colors: {Red, Red to Orange, Orange, Orange to Yellow, Yellow, Green, Green to Cyan, Cyan, Cyan to Teal, Teal, Blue, Blue to Purple, Purple to Pink, Pink}
int red[] = { 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 40, 130, 180, 255 };
int green[] = { 0, 10, 30, 150, 255, 255, 255, 255, 127, 63, 0, 0, 0, 0, 0 };
int blue[] = { 0, 0, 0, 0, 0, 0, 127, 255, 255, 255, 255, 255, 255, 255, 255 };
int code[] = { 16195807, 16191727, 16199887, 16189687, 16197847, 16228447, 16224367, 16232527, 16222327, 16230487, 16212127, 16208047, 16216207, 16206007, 16214167 }; //These are the corresponding codes on the IR remote
int color;

//Text Setting
//Font attribution to "https://www.freepik.com/free-vector/flat-design-digital-display-font_44130971.htm#fromView=keyword&page=1&position=4&uuid=019dbb76-085e-4d53-a7cb-b3f352b17a71&query=Pixel+art+alphabet" Image by freepik
//The alphabet[] array indicates which LED should light up on 7x7 LED matrix. The alphabet[] array is in 2D and each row corresponds to the characters[] array.
char characters[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '?', '!', '<', '+', '-', '*', ' ' };
bool alphabet[][49] = { { 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0 },
{ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0 },
{ 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 },
{ 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 },
{ 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } };

long previousMillis;
int brightLedCount;
int blackLedCount;
int characterCount;
int hue;

uint32_t data; //This is where the code sent from the IR receiver will be stored
int mode = 1;

void LED_Control(void* parameter) {
while (1) { //Since this function is running outside the loop(), there is while(1) to make it act similar to loop().
if (ir_recv.nec_available()) { //ir_recv.nec_available() triggers when a new code is received.
Serial.print("IR Protocol: ");
Serial.print(ir_recv.protocol());
Serial.print("IR Code: ");
Serial.println(ir_recv.data()); //Serial print the protocol and the code
if (ir_recv.data() != 4294967295) { //My IR remote sends an invalid code when I hold on to the button, so this prevents the invaild code from storing in "data".
data = ir_recv.data();
brightLedCount = 0;
blackLedCount = 0;
characterCount = 0;

if (data == 16187647 || data == 16220287) { //Brightness Control
if (data == 16187647) {
brightness += 25;
} else {
brightness -= 25;
}
brightness = constrain(brightness, 0, 255); //Constrain to 0-255
FastLED.setBrightness(brightness);
}

if (data == 16240687) { //Change to Different Mode
mode++;
if (mode > 4) { //Change it to the number of modes you would like to have
mode = 1;
}
}

for (int i = 0; i < 15; i++) { //Color Change //Change "15" to the number of buttons you are using to switch colors
if (data == code[i]) {
color = i;
break;
}
}

if (data == 16203967) { //Turn Off
mode = 0;
}

if (data == 16236607) { //Turn On
mode = 1;
}

FastLED.clear(); //Clear the LED Matrix
}
}
if (mode == 1) { //This mode allows the LEDs to turn on one by one and turn off individually again after all have been lit up
if (millis() - previousMillis > 500) { //0.5s delay until the next LED turns on
previousMillis = millis();
if (brightLedCount < 49) {
leds[brightLedCount] = CRGB(green[color], red[color], blue[color]); //Somehow the parameters for RGB are actually GRB
brightLedCount++;
} else if (blackLedCount < 49) {
leds[blackLedCount] = CRGB::Black;
blackLedCount++;
} else {
brightLedCount = 0;
blackLedCount = 0;
}
}
} else if (mode == 2) { //This mode turns all LEDs
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB(green[color], red[color], blue[color]);
}
} else if (mode == 3) { //Rainbow Effect
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = CHSV(6 * i + hue, 255, 255);
}
hue += 1; //Adjust this to change the speed
} else if (mode == 4) { //This mode allows for text to print
char text[] = { 'H', 'E', 'L', 'L', 'O', ' ', 'W', 'O', 'R', 'L', 'D', '!', ' ' }; //Each character is stored indidually including space(' ')
printText(text, 13); //Adjust the second parameter to the number of characters in your message, including space(' ')
}
FastLED.show(); //Turn on the LEDs according to how we mapped it on leds[]
vTaskDelay(1); //Having a delay is crucial in the code because without it, loop() does not get to run as LED_Control() has more priority
}
}

void printText(char text[], int length) { //Function to print text
if (millis() - previousMillis > 1000) { //1s between each character
if (characterCount < length) { //If it finished printing out the text, repeat it again
FastLED.clear(); //Clear LED matrix before writing a new character

if (characterCount > 0) { //If a letter is repeated, then blink to indicate that the code is actually printing a new character
if (text[characterCount] == text[characterCount - 1]) {
FastLED.show();
vTaskDelay(200); //Currently set at 0.2s blink period, adjust it as you like
}
}

for (int j = 0; j < 43; j++) { //Change "43" to the number of characters mapped out to display, currently there are 43 characters, which you can see in characters[]
if (characters[j] == text[characterCount]) { //Compare text[characterCount] to characters[j] to find out which row of alphabet[] to use
for (int i = 0; i < NUM_LEDS; i++) { //Find out which LED to turn on or not
if (alphabet[j][i + 1] == true) {
leds[i] = CRGB(green[color], red[color], blue[color]);
}
}
break; //Exit the for() loop because the desired character is already printed on the LED matrix
}
}

previousMillis = millis();
characterCount++;
} else {
characterCount = 0;
}
}
}

void setup() {
Serial.begin(115200);
FastLED.addLeds<WS2813, DATA_PIN, RGB>(leds, NUM_LEDS); //Switch "WS2813" to the type of LED strip you are using
xTaskCreatePinnedToCore(LED_Control, "LED_Control", 2048, NULL, 10, NULL, 1); //We are creating a task to run LED_Control() on Core 1 as well like loop()
}

void loop() {
ir_recv.task(); //This function receives the incoming code from IR remote //The reason why LED_Control() is not placed inside loop() is that ir_recv.task() waits infinitely until it receives a code, which makes it impossible to display patterns, unless the multitasking feature in ESP32 is enabled like here
}

This is the main code for the LED matrix. I commented on what each part does, so have a look at it and modify it as much as you would like. Make sure to change the IR codes to yours.

The two libraries I used are FastLED and Freenove IR Lib for ESP32.

Some of its modes are rainbow effect and text display. It can also change colors and brightness. Text function would only work on 7 by 7 LED matrix because I mapped the characters specifically to this size.

Decoration

k.jpg
KakaoTalk_20260120_010355710.jpg
KakaoTalk_20260120_010355710_01.jpg

Now, it is time to decorate the LED matrix to blend in with the graduation hat. First, peel off the adhesive tape on the back of LED strips and attach it on a piece of cardboard. I attached a SVG file to guide where to attach the LED strips on a 24cm by 24cm cardboard. Then, place a white sheet of paper on top of the LED matrix to mark where the LED is exactly located. Cut out the squares and each LED should fit tightly into the openings.

Downloads

Finish

s.jpg
KakaoTalk_20260119_235337827 (3).gif

This is the finished product! I haven't glued the battery and ESP32 on the graduation hat because this is my brother's and I have not received mine yet, but once I do, I would be able to hide the electronic under the brim. There are some improvements I can make like adding scrolling text to scroll and emojis. I will be keep improving it until the graduation date comes and if you have any questions or concerns, please comment down below. Thank you for reading my first Instructables!