The “Super Matrix” a 14 x 14 Arduino Powered RGB Matrix

Last year for my mother’s birthday I was tasked with figuring out what I should give her as a gift. While I could have purchased a card or sweater, I decided to be unique and create a one of a kind gift! During this time, I was introduced to single line addressable LEDs and wanted to make a project using the LEDs. Thus, I came up with the idea to create a 14 x 14 RGB matrix, using the PL9823.  The matrix would add a bit of color to my mom’s work desk and give her something to remind her of me.

Since I already knew what components I wanted to use for this project, I did not spend much time designing the circuit. The reason I wanted to use self addressable LEDs, like the PL9823, was the simplicity to implement large arrays with only a single data pin.This meant I did not have to spend time writing code for multiplexing or designing the driving hardware for a matrix consisting of 196 LEDs. The LEDs feature all the necessary driving hardware internally, so all I needed was to provide power, ground, and a data signal which would be passed through the LEDs. If you are interested in learning more about the LEDs, I wrote a separate blog post here where I talk more about the LEDs in detail.

To continue with the idea of simplicity, I decided to use an Arduino as the “microcontroller” to control the LEDs and read the button input’s from user. It is important to note that in the circuit, the actual microcontroller is an Atemega328p which is what the Arduino Uno uses. Arduino isn’t a microcontroller but a development kit for various microcontrollers, which run a custom programmable firmware. This firmware allows for the microcontroller to be programmed over serial using the Arduino IDE and for the user to easily convert their code to other Arduino boards if they need to add more functionality. For this project, I was planning on using the Arduino Uno bootloader ( The firmware which is loaded onto the Atmega328P) which required a few supporting components. The components included a 16 MHz crystal due to the firmware requiring a 16 MHz clock (The Atmega328 does not have that internally) and some capacitors for the analog and power pins. Looking back, since I did not use any of the Arduino’s ADC channels, I could have removed capacitor C3. Additionally 8 MHz based booatloaders exist for the Atmega328p, so I could have saved room by using the slower, internal clock.

For the project I wanted the user to be able to cycle through colors, patterns, and turn the display off. To implement this feature I added 3 push buttons with 10K pull down resistors to make sure the input is always 0 when the buttons aren’t pressed. In hindsight, I forgot to add an important feature to the pushbuttons, debouncing! These buttons aren’t perfect and while you can press the button once, it might create oscillations which makes the microcontroller read multiple presses. In my usual designs, I tend to use the hardware implementation of debouncing instead of software but I forgot to do either! If you are interested in learning more about button debouncing, Jack Ganssle wrote this guide found here.

To power the circuit, a 5V 2A power adapter was used. The circuit featured both a barrel jack for the adapter and a jumper on the programmer header to allow the circuit to be powered through usb when testing. After completing the project, I realized my power budget was too low in certain situations. When the matrix would run at maximum power, all LEDs being white, the matrix would flicker and not properly handle the power. According to this post from lane boys RC, found here, each PL9823 at maximum power consumes about 55 mA of current. This means the circuit would consume 196 * 55mA = 10.78A of current to be full white! This explains why I would have flickering during certain tests, since the power supply would not be able to deliver the current needed. To avoid this power budget issue, I had to add a 1000 uF capacitor connected from 5V to GND, which helped combat some of the flicker. Additionally, I designed the patterns on the display to never reach these flickering states. Below is the schematic for the microcontroller circuit.

MCU Circuit

After the Schematic was completed, the next step was to design and order the PCB. To making the PCB was due to placing the LEDs equidistant from each other with a proper gap. To do this, I used the old trick known as trial and error. I would place some spaced LEDs on the PCB and then print a 1:1 scaled image of the board. Then I would physically push the LEDs through the paper and see if I’m satisfied with the fit. This printing technique is an old trick I use to make sure packages are correct for components and if all components fit before ordering a PCB.

After I finished laying out the LEDs, I placed the rest of the components. I made sure to place most of the components on the top of the board. This allowed me to make a case that covered the back of the PCB and allowed for the parts to be displayed as part of the decoration. The buttons were placed in the middle, the power jack to the left, and the programmer’s connection on the right.  To continue with the theme of simplicity I used a ground plane on the bottom side of the PCB and a VCC plane on the top of the PCB. A plane is basically a giant piece of copper which is not routed and used for a signal instead. Since I used planes for the VCC and ground of the LEDs, all I needed was to draw the data signal trace. This trace went from the microcontroller to the top led and with a snake like pattern the LED would feed the next LED’s input using its data out. Below is the image of the PCB.

PCB Layout in Eagle

Once the PCB was finished, I generated the gerbers files. These files tell the manufactures how to make each layer of the PCB. Since eagle automatically displays the component name on the silkscreen, I had to modify the silkscreen gerber to not include 196 overlapping LED names. Once the gerbers were complete, I decided to go with advanced circuits to order the PCB. The board cost $33 for students + a ridiculous shipping fee. After the price I paid in shipping, I would never return to advanced circuits.

Once the board arrived I began soldering the components. I first soldered all surface mount components, since they are the easiest to solder first. Then I added the rest of the microcontroller and power components. These components were soldered first so they could be used to test the LEDs that were soldered. After completing each row of LEDs, I would power the board and run some test code to verify the LEDs are 100% working.

Once the board was finished, I used the laser cutter at my work to design a case for the board using plastic. Three pieces were cut for the case, these pieces were the transparent front cover, the black back cover, and a black border to place around the PCB. Sadly, I lost the laser cutter files for these pieces, so I can’t show them to you. They are pretty straight forward to make and took less than an hour to complete.

Once the case was finished, the board was mounted using double sided tape. Then the remaining parts were screwed in using M3 screws. To give space for the front cover, standoffs were used. Below are the pictures of the PCB and the finished product, the capacitor mentioned earlier for the power issues can be seen sticking out. I think it gives the project a unique look, showing the rapid repairs found in the engineering world.

Board Being Assembled

Front of Completed Project (I’m a Terrible Photographer)

Fancy Angled Shot of the Completed Project

Back Side of the Completed Project

Finally, once the board was mounted in the case, the code was written and uploaded to the board. The code uses a library for controlling the LEDs written by Tim from CPLDCPU, which can be found here. Since I was in a rush to complete this project, the code is definitely amateur and not pretty. Looking back, I should have used hardware interrupts for a better response from the pushbuttons, due to some pushbutton presses being skipped. I did write the patterns to work well with the buttons but since I rushed the code, I used delays for some of the patterns that lasted over 100ms!!! At the bottom of this post, I attached the code without comments. If you have any questions about the code, feel free to ask below.

It’s been over a year since I completed this project to much success! My Mom loves this gift and all her coworkers have expressed interest in purchasing one. Though there is a “demand,” I will not be making more of these matrices. The cost to build this including components, case, and PCB is roughly $150 (rounding up). Additionally I will say there were plenty of mistakes made from rushing this project. Currently I don’t see a future in making a second version but if I would, the PCB, Schematic, and board would be modified to add proper fixes. If you made it this far, pat yourself on the back! I know this is a pretty long post but this entire project was an adventure from design to hours of soldering. I dug deep into my hard drive and my mental memory to write this post, to document the whole process. Below is all the files I have saved and some videos of the matrix in action!

Schematics and PCB:

(Right Click -> Save Target As)

Schematic

PCB

Code:

supermatrix.ino

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// use the cRGB struct hsv method
#define USE_HSV
 
#include 
 
#define LEDCount 196
#define outputPin 9
 
WS2812 LED(LEDCount); 
cRGB value;
cRGB clearVal;
int h = 0;   //stores 0 to 614
byte steps = 2; //number of hues we skip in a 360 range per update
 
byte sat = 255;
byte val = 100;
 
long sleep = 100; //delays between update
int state = 0;
int mode = 0;
int color = 0;
int prevColor = 0;
bool dispON = true;
int c = 0;
int endClear = 196;
int delayCount = 0;
int boxCount = 1;
int stateSave = 0;
long randomColor;
long randomNumber;
bool I[196] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
bool heart[196] = {
  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,0,0,0,0,1,1,0,0,0,
  0,0,1,1,1,1,0,0,1,1,1,1,0,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,0,1,1,1,1,1,1,1,1,1,1,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,0,1,1,1,1,1,1,0,0,0,0,
  0,0,0,0,0,1,1,1,1,0,0,0,0,0,
  0,0,0,0,0,0,1,1,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
bool U[196] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,1,0,0,0,0,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,0,1,1,1,1,1,1,1,1,1,1,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
  bool M[196] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,1,0,0,0,0,1,1,1,1,0,
  0,1,1,1,1,1,0,0,1,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,1,1,1,1,1,1,1,1,1,1,1,1,0,
  0,1,1,1,0,1,1,1,1,0,1,1,1,0,
  0,1,1,1,0,0,1,1,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
  bool O[196] = {
  0,0,0,0,0,0,0,0,0,0,0,0,0,0,
  0,0,0,0,1,1,1,1,1,1,0,0,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,1,1,1,0,0,0,0,1,1,1,0,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,1,1,0,0,0,0,0,0,0,0,1,1,0,
  0,1,1,0,0,0,0,0,0,0,0,1,1,0,
  0,1,1,0,0,0,0,0,0,0,0,1,1,0,
  0,1,1,0,0,0,0,0,0,0,0,1,1,0,
  0,1,1,1,0,0,0,0,0,0,1,1,1,0,
  0,0,1,1,1,0,0,0,0,1,1,1,0,0,
  0,0,0,1,1,1,1,1,1,1,1,0,0,0,
  0,0,0,0,1,1,1,1,1,1,0,0,0,0,
  0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
 
 
void setup() {
  LED.setOutput(outputPin);
  LED.setColorOrderRGB();
  clearVal.r = 0;
  clearVal.g = 0;
  clearVal.b = 0;
  clearDisplay();
  pinMode(6,INPUT);
  pinMode(5,INPUT);
  pinMode(7,INPUT);
  randomSeed(analogRead(0));
}
 
void loop() {
 processInput();
 if(dispON == true)
 {
  switch(mode)
  {
    case 0:
    loveMOM(100);
    break;
    case 1:
    rainbow();
    break;
    case 2:
    count();
    break;
    case 3:
    randomPixel();
    break;
  }
 
  state++;
  }
}
 
void processInput()
{
  if(digitalRead(6) == HIGH)
  {
    delay(10);
    if(dispON == true)
    {
      clearDisplay();
       clearDisplay();
      dispON = false;
    }
    else
    {
      dispON = true;
      state = 0;
      delayCount = 0;
    }
    while(digitalRead(6) == HIGH);
  }
    if(digitalRead(7) == HIGH)
  { 
    delay(10);
    c = 0;
    endClear = 196;
    mode++;
    if(mode == 4)
    mode = 0;
    while(digitalRead(7) == HIGH);  
  }
  if(digitalRead(5) == HIGH)
  {
    delay(10);
    color+=5;
    if(color == 360)
    color = 0;
  }
}
 
void rainbow()
{
  Cycle();
 
  for(int i = 0; i < LEDCount; i++)
  {
      LED.set_crgb_at(i, value);
  }
  // Sends the data to the LEDs
  LED.sync();
 
  delay(100);
}
void count()
{
   for(int i = 0; i < endClear; i++)
  {
      LED.set_crgb_at(i, clearVal);
  }
  LED.sync();
  value.SetHSV(color, sat, val);
  if(color != prevColor)
  {
    prevColor = color;
    for(int i = endClear; i < 196; i++) { LED.set_crgb_at(i, value); } LED.sync(); } LED.set_crgb_at(c,value); LED.sync(); c++; if(c == endClear) { c = 0; endClear--; if(endClear == 0) { endClear = 196; } } delay(100); } void randomPixel(){ randomColor = random(360); randomNumber = random(196); value.SetHSV(randomColor, sat, val); LED.set_crgb_at(randomNumber, value); LED.sync(); } void loveMOM(int d) { switch(stateSave) { case 0: charI(); //delay(d); break; case 1: charHeart(); // delay(d); break; case 2: charU(); // delay(d); break; case 3: charM(); // delay(d); break; case 4: charO(); // delay(d); break; case 5: charM(); // delay(d); break; } delayCount++; if(delayCount == d) { delayCount = 0; stateSave++; if(stateSave > 5)
    stateSave = 0;
  }
}
 
//Drawing Stuff
void charHeart()
{
  value.r = 50;
   value.b = 0;
   for(int i = 0; i < 196; i++)
  {
      if(heart[i] == true)
      LED.set_crgb_at(i, value);
      else
      LED.set_crgb_at(i,clearVal);
  }
  // Sends the data to the LEDs
  LED.sync();
}
void charI()
{
    value.r = 0;
  value.b = 50;
  value.g= 0;
   for(int i = 0; i < 196; i++)
  {
      if(I[i] == true)
      LED.set_crgb_at(i, value);
      else
      LED.set_crgb_at(i,clearVal);
  }
  // Sends the data to the LEDs
  LED.sync();
 
}
void charU()
{
  value.r = 0;
  value.b = 50;
  value.g= 0;
   for(int i = 0; i < 196; i++)
  {
      if(U[i] == true)
      LED.set_crgb_at(i, value);
      else
      LED.set_crgb_at(i,clearVal);
  }
  // Sends the data to the LEDs
  LED.sync();
}
void charM()
{
  value.r = 0;
  value.b = 50;
  value.g= 0;
   for(int i = 0; i < 196; i++)
  {
      if(M[i] == true)
      LED.set_crgb_at(i, value);
      else
      LED.set_crgb_at(i,clearVal);
  }
  // Sends the data to the LEDs
  LED.sync();
}
void charO()
{
  value.r = 0;
  value.b = 50;
  value.g= 0;
   for(int i = 0; i < 196; i++)
  {
      if(O[i] == true)
      LED.set_crgb_at(i, value);
      else
      LED.set_crgb_at(i,clearVal);
  }
  // Sends the data to the LEDs
  LED.sync();
}
void clearDisplay()
{
     for(int i = 0; i < LEDCount; i++) { LED.set_crgb_at(i, clearVal); } // Sends the data to the LEDs LED.sync(); } void Cycle() { value.SetHSV(state, sat, val); if(state > 360)
  {
      state %= 360;
  }
}

 

This entry was posted in Electronics, Portfolio. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *