Hexagonal LED Display
I was experimenting with LED strips and had the idea of creating a matrix display which is not made up of quadratic pixels but of hexagonal pixels. The display is contollable with touchpads.
Since I was not sure if this is a good idea I started with a prototype.The prototype lead me to this display which has no visible hexagons anymore. In this instructable I will show you how to build it and where the hexagons are.
I have created videos for some of the steps.
Please have a look at them to get a quick overview. They are also embedded in the steps.
Supplies
- A Picture Frame. Mine is about 40x30 cm, but you can use any size you want
- An individually addressable LED strip like a WS2812B or SK6812. The SK6812 has additional white LEDs which are not used in the standard coding, but you are free to use it and create your own patterns. The coding is prepared for it, and you can adapt the coding easily. I use a WS2812B with 30 LEDs per meter. I used about 4 meters for my project (111 LEDS).
- An Arduino microcontroller. Choose the one you are familiar with and which fits your size requirements. You need only one. The picture just shows some of your choices
- Some wire
- A separate 5V power source. I used a USB cable and cut it in two, then connected it to a power bank. You can also use a 5V power supply. A sigle 18650 Li-Ion battery could also work.
- Copper foil or other metallic objects as sensor pads. Tin foil is very hard to solder, but works fine once it is soldered.
- A soldering iron and other equipment you need for soldering like solder, wire cutter etc..
- Other handicraft equipment like glue and black paint. See the build video for details.
The Prototype
You do not need to build a prototype. I just want you to show how I came to the idea of creating this display.
I had the idea of creating a LED matrix display which is not made up of quadratic pixels but of hexagonal pixels. So I printed a hexagonal pattern which you can create on your on online e.g. here and glued it to some cardboard. Then I cut and glued my LED strip to it. I soldered them together, so they can be adressed as one strip. Since I wanted a hexagonal display I glued some hexagonal framing on a piece of paper which should act as diffusor.
With a sketch for a linear strip I tested it with my diffusor and also with a plain sheet of paper.
The hexagonal framing worked fine when it was near to the LEDs, but it was also interesting when it was further away, because nice shadows appeared. This could of course be avoided with deeper framing. The tests with just a paper where also quite interesting, so I decided to go this way.
Since there have been build enough normal square pixel displays, it is time for something different. And since I didn't wanted to go the pixel art way, but more the abstract way this was the way to go. By the way... I wonder how pixel art would look like on a hexagonal display?
So the decision was done to follow the diffusor only way...
Planning
My picture frame is 40x30 cm and the LEDs on the strip are 3.33 cm away from each other (1meter/30LEds = 3.33cm). The hexagonal pattern can be reduced to equal sided triangles as shown in the pictures above. The distance of the strips to each other has to be the height of the triangle. It can be calculated by multiplying 3.33 with sqrt(3)/2 which is about 2.89 cm.
I need to decide in which orientation I put the LEDs in: Aligned to the long or short side. I decided to have the strips along the short side of the frame. Therefore I can get 30 cm / 3.33 cm = 9 in there. The long side is 40 cm, but the distance between strips is closer as described above. Therefore I can put 40 cm / 2.89 cm = 13.8 in, rounded down to 13. The resulting resolution is 13x9 pixels. Well not exactly. Since it is not a square pattern, every second row has an offset of half of the LED distance and can fit only 8 LEDs in. So I prepared 7 strips with 9LEDs and 6 with 8LEDs. I marked the backboard of the frame with parallel lines in a distance of 2.89cm starting from the center.
Build the LED Board
After making the plan, I painted the inside parts of the picture frame black to avoid unwanted reflections. Then I glued the LED strips to the board. They are usually self-adhesive on the back, so this is pretty easy. Start in the middle with the long one and alternate the odd rows with short ones in the opposite direction. Make sure the distances are correct as described in the previous step since the software uses physical positions instead of pixel counts. The LED strips need to be oriented in a zig-zag way to avoid long cables. The data line always needs to follow the arrows on the led strip. See the pictures for details.
Now solder all strip ends with +5V and Ground to the same pins of the next strip. Also solder a connection wire to the beginning of the strip. There has to be a Din at this end. This can also be seen on the picture obove.
Double check that all +5V/GND pins are connected to other +5V pins and all Do pins are connected to Din pins.
Connect the Arduino
Connect your power supply to the display. I have used a USB cable, cut it in two and connected the black wire(GND) with the LED strip and the Arduino. The red wire (+5V) has to be connected with the +5V of the LED strip only. Don't connect it with the Arduino, otherwise you get problems when you connect the arduino to your computer for programming.
Connect the Arduino GND to the GND of the LED strip if not already done and the Pin 2 of the Arduino with the Din of the LED strip.
Connect your sensor pads to the A0, A1,A2, A3 pins of the Arduino. You can use up to 4 pads, but also less.
Program the Arduino
Programming the Arduino is simple. When you have never done this before see the official page: https://www.arduino.cc/en/Guide how to do this. But then you should probably do a simpler project first and come back later. I use platformio as my favourite IDE since it is much more developer style.
The software is the most complex part of this project, but you usually don't need to touch it unless you use RGBW strips. The project can be configured via a serial console on the computer. See the step Configure Your Display below. For details how the software works see the step Create And Share Your Own Patterns below.
The software can be found in the Github repository:
https://github.com/JustMakeAnything/HexagonalDisplay
When using the Arduino IDE you need to install the FastLED , the Adafruit NeoPixel library and the ADCTouch library manually before compiling. The FastLED library is used for advanced computations only, not for drivig the LEDs
When using PlatformIO you may need to adapt the Arduino type you are using in the platformio.ini file. It is set to an Arduino Uno by default. The libraries are automatically installed by PlatformIO.
Test It
Connect your power source and connect your arduino to your computer. The display should light up now. The software is configured to light up 30 LEDs. When you have more, only 30 of them light up. This is correct and we adapt this now.
Go to the serial monitor:
- Little connector icon in the bottom right in Visual Studio Code
- Icon on the top right of the Arduino IDE https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-serial-monitor. Set the baud rate to 9600.
- You can also use a terminal program like putty and open the port the Arduino is connected to. On windows this usually something like COM[XX].
The Arduino prints a menu here:
[L]ed Strip [S]ensors [P]atterns show [M]enu
Press L on the keyboard. When using the Arduino IDE you also have to press enter afterwards. Now the LED menu is shown:
[C]heck RGBW [A]mount show [M]enu [.]Back
Press A:
Amount:
Enter the amount of LEDs you have. The numbers are not shown when you type, just type your number and press enter.
Afterwards we check the correct colors. Go back to the previous menu by pressing . (yes the dot) and press C. The strip should light up the first LEDs in the colors Red-Green-Blue-Off-White. When you have a strip with separate white it should show an additional White at the fourth position. But probably it shows something completely different since these strips have a slightly different protocol. To fix this, you must modify the coding.
Open the constants.h file and change the line with
#define STRIP_TYPE NEO_GRB
To the type of your strip. Usually this is NEO_GRBW for SK6812
Upload the coding again and the colors should look fine. When the color sequence is not correct, use a different combination of R G B letters. Each one has to occur exactly once, just swap them.
Be aware that the coding has a hard limitation to 150 LEDs. You can enter more here, but only 150 are used. See the file constants.h for the line
#define MAX_NUM_LEDS 150
and adapt it to your needs. Be aware that the limit is set to 255 LEDs since the Arduino has only limited memory.
When some LEDs do still not light up, there may also be a cable issue. Receck your cabling.
Final Assembly
You may want to do the configuration step below before this one to see if everything is working fine.
For the final assembly we need to create distancers from the back panel to the front of the picture frame. I use foam material I found laying around and painted it black. You could use anything else which holds your back panel in a distance of around 2cm (about 1 inch) from the glass. You can experiment with the distance as shown in the video of the protoype step. I glued them to the back panel with hot glue and made sure they are slim enough to not obstruct the glass.
The diffusor is just a blank sheet of paper before the glass. You can also replace the glass with frosted glass.
The sensor pads need to be accessible from the outside, so make sure you put it somewhere you can reach them easily. They may also work through thin material, but make sure this works by testing it before. You can see how to calibrate and test the sensors in the configuration step below.
Since I don't know what frame you have, I can't give you an advice how to assemble it it detail, so use your imagination. Just put everything together and you are done! The rest of the configuration has to be done via the USB cable of the Arduino, so make sure you can access it when it is assembled, or do the configuration step below before assembly.
Configure Your Display
To configure the sensor pads and patterns, use the serial console as described in the Test It step. Since the sensor pads may change sensitivity according to the cabling this step is (re)done after the assembly.
Choose S from the menu. The sensor menu is shown:
[A]mount [C]alibrate Assign [F]unc show [M]enu [.]Back
You can set the amount of sensors you want to use by choosing A. As before your typing is not shown, just enter the number (up to 4) and press enter.
You may need to calibrate your sensors. This function can also be used to test them. Press C to go to the calibration function. This is shown over and over again:
|----o----------------|x o |x o |
Touch a sensor and you can see something like this:
|XXX-o----------------|xxxxxxxxxOxxxxxxx |xxx o |
It shows the read values from the sensor pads. The second sensor was touched, and the other sensors also got a signal. This happens when the wires are next to each other, what they usually are. At least when they reach the Arduino. You can change the threshold value for the sensor which is selected (not necceserily touched) by pressing O and P. Choose a treshold which is easily reached by touching the sensor, but not reached when the other sensors are touched. The selected sensor is the one with the dashes (-). You can switch the selected sensor by pressing Q or W. The dot(.) leaves the calibration and saves it to the Arduino. The key layout is also shown when you press a key which is not valid.
By pressing F you can assign functions to the sensor. The sensor numbers are analog to the position in the calibration step. 1 is the leftmost each following increases by 1. Type a number and a menu is shown:
[O]n/off >[N]ext P [P]rev P [B]right [F]avour [T]rigg. show [M]enu [.]Back
It is a little abreviated due to memory saving efforts. The mark > shows the currently active function. You can choose any function by pressing the letter in backets.
- [N]ext P : Switch to next pattern
- [P]rev P : Switch to previous pattern
- [B]right : Change brightness in 3 predefined steps
- [F]avour : Switch to favourite pattern
- [T]rigg. : Trigger function. This is used by some patterns. Try it on the fire pattern.
This assigns the function to the sensor. It is possible to assign all sensors with the same function. Usually you want to have at least a Next Pattern function. Go back and repeat this for the other sensors as well.
To choose your patterns, select P from the main menu. It shows this:
>1< 2 3 4 5 >F< q/w=patt o/p=move f=fav space=on/off .=back
At the same time the pattern shown is changed to the one marked with > <. The F indicates the favourite pattern. This is the one which is selected when you start the Arduino or when you touch the sensor assigned to the favourite function. You can set it to the current pattern by pressing F. The current pattern can be changed by pressing Q or W. Pressing space( ) moves the number to the bottom and shows that this pattern is no longer shown when cycling through with the next pattern function. With the O and P keys you can change the sequence of the patterns. When you want new patterns, you have to add them in the coding. See the last step for details.
Create and Share Your Own Patterns
I have also created the video to give you an impression what the software does.
You can also create your own patterns. It is more like scripting than programming as long as you are happy to just move circles around, which most of the predefined patterns do. You can also program the LEDs individually by using a callback. If you are happy with the existing patterns or not a developer you don't need to read this. But if you are, I invite you to play around with the possibilites. It is not complicated to create own patterns.
The pictures give you an impression what can be done:
- The cyan hexagon is a single circle with hard borders. It belongs to the patternInteractive2D pattern which can control one circle via the keyboard. See the source file for details.
- The Blue/White pattern is the cloudySky pattern. It randomly generates circles at random positions and random sizes and let them move around. The move direction is also randomly changed.
- The multi color lines is a callback implementation of 90s computer demo plasma effect. It is basically a mathematical computation related to the pixel position.
- The red circle is also a callback implementation which sets the color according to the distance to the center of the display.
- The rainbow pattern is not optimized for 2D. It just fills the LEDs with rainbow colors according to their position in the strip.
In the file patterns2D.cpp is the coding. You need to create a function which has to be added to the patternList in patterns.cpp
When you have created a pattern, share it in the comments. It shouldn't be more than 30 lines. All my patterns even the complex Fire and Plasma have less than 30 lines. You can also create a pull request in github.
Here is a short overview of the functionality:
To add a circle you just need to call the function
pattern2D(param2);
param2 can be initialized by:
param2 = patternPDefault2;
This won't show anything because you need to set a size of the circle. Values of around 1000 up to 5000 are fine.
param2.size = 2000;
You also need to set a color:
param2.baseColor = getLedColor(255,0,0,0);
the numbers are the RGBW values. In case you don't have white LEDs in your strip the fourth(W) value does not have any influence.
This is enough to show your circle. You can call the pattern2D multiple times to create mutliple circles. The maximum is defined in MAXPOSITIONS. You need of course change some of the values in between, otherwise the same circle is drawn multiple times whitout any influence on the display.
The circles can move around by setting the speedx and speedy values:
param2.speedx = 20; param2.speedy = 30;
The coding will automatically move the circle around and wrap it around behind the borders of the display. When you want the circle to move other than straight, you need to change this values. See the patternFire2D for an example.
To set a starting position set:
param2.positionx = CENTERX+2000; param2.positiony = CENTERY-1000;
This sets the circle to this position once at the beginning. Changing the values does nothing unless you call
patternInitialize2D();
When you draw a circle over another there are the following possibilites what happens. It can be controlled via the merge variable which need to be filled with a value from the MergeType enum.
- MOver blends the existing color with the new one according to the intensity parameter.
- MCopy Just overwrites the existing pixels.
- MMax calculates the maximum value vor each color (RGB). The best way to blend similar colors into each other.
- MMedian blends the color with the previous 1:1.
param2.merge = MMax;
To change the appearance of a circle you can uses shape. The shape varies the intensity of the color according to the distance to the center. The following is possible
- Solid All pixels are the same intensity
- SawLeft Linear intensity drop from center.
- SawRight Linear intensity rise from center.
- SingleRight One pixel at radius is on, all other off
- Sine Intensity rises and drops from center according to a sine curve
- SineLeft Intensity drop from center following a sine curve
- SmoothRight Like SineLeft but with a hole in the middle for larger radiuses
param2.shape = SawLeft;
For all other possibilites you can use the callback. This just runs through all LEDs and calls the passed callback routine for each LED. It passes the position and the distance to the center (position). You can also combine the callback with the circles. The callback has to implement the following signature
uint32_t callback(uint8_t ledNumber,uint16_t distance,uint16_t physX, uint16_t physY)
Where the returning uint32_t is the color to set.
Conclusion
Overall I am very happy with the display. I hope I have inspired you to build your own. When you have some experience with soldering it is really not complicated. The part which was most complicated and took the most time was the software, and you don't have to create it since it is available in the github repositiory for free.
The decision for hexagonal pixels was a good idea. Also the decision to not separate them with covers. The patterns look very nice especially when considering that the whole display has a resolution of 9x13 pixels. This is less than a sprite on the Commodore64, or 0.000117 MP!
Since quadratic pixels have a higher distance to the next diagonal pixels than to the next orthogonal pixel, there would be brighter and darker areas on the display. The hexagonal pixels however have all the same distance to each other. This makes the blending of the LEDs colors into each other much smoother. Round shapes are also displayed nicer. This can be seen very well in the plasma pattern. It looks like it was designed especially for such a display.
The software part was also very interesting since I gave up the idea of adressing the LEDs individually, but use the physical position. The software is able to control displays with arbritrary layouts (whith adaptions), but is limited to show simple shapes. This is not a problem for the low resolution display which is nevertheless not able to display complex shapes.
I have already an idea of creating a display with randomly distributed LEDs. Or a reconfigurable display. This would then have other challenges like finding the excact positions to overcome...
Thank you for reading so far. In case you have any questions leave them in the comments below.