Arduino LED Button Pad That Drives Processing Animations
by amygoodchild in Circuits > Arduino
14393 Views, 99 Favorites, 0 Comments
Arduino LED Button Pad That Drives Processing Animations
What
This button pad is made using a PCB and other components manufactured by Sparkfun. It is driven by an Arduino Mega. Each button is nice and squishy and satisfying to press, and it has an RGB LED inside!
I've been using it to control animations I've coded in Processing. The button pad sends a message whenever a button is pressed, saying which button it was. Processing receives these messages and changes variables in the sketch depending on what was pressed.
Why
LEDs are cool.
Buttons are fun to push.
Animated geometric patterns are nice.
I wanted to combine all three.
I took this project to a party, projected the visuals on the wall and let people play with the buttons. It could also be used by a VJ in a more performative way, much like a midi controller but more DIY.
How
There are four main parts to this project.
The attached Youtube video gives a good look at how the button pad goes together. This Instructable covers that as well as the Arduino and Processing code - (additional videos for those are in the works)
- Putting the button pad together - Starts in Step 1
This involves preparing the components and soldering them to the PCB
- The Arduino code - Starts in Step 10
For this, we need an understanding of matrix scanning, which I will talk through.
- The Processing code - Starts in Step 24
There are endless possibilities here, I'll talk through one example I've made so far.
- Getting the Arduino to send messages to Processing - Step 16 for sending, Step 30-31 for receiving
This is nice and simple, it sends the message over a serial connection.
Level
I try and write my tutorials in such a way that someone with absolutely no knowledge at all can at least follow along. You might find it helpful to first watch some introductory tutorials about Processing. I'd start with Daniel Shiffman's YouTube channel.
Code
All of the code (Arduino and Processing) is on my github here.
Credits
I learnt a bunch from this tutorial https://learn.sparkfun.com/tutorials/button-pad-ho... and much of the Arduino code is from there, although I have edited it to work slightly differently from any of the examples there.
The Components!
- 16 x 5mm RGB LEDs (not addressable ones, just regular common cathode ones)
- 16 x 1N4148 diodes
- Silicone button pad
- Button pad PCB
- Arduino Mega
- Jumper cables
(There's also a bunch of stuff you can get from Sparkfun to house the whole thing a bit more neatly, but I have not done this)
Prepare the Diodes
Bend each diode and then push it through the PCB.
The legs stick out on the button side, which we don't want. So take the diode out again and cut the legs short. (You might have some snips that will let you cut the legs flush with the board while it's still in there which will make your life easier, but I only had normal scissors so I had to pull them out to cut them short enough.)
It is super important to bend the legs and push them through the PCB before you cut them short. If you cut them short first then you won't be able to bend them into shape.
Make 16 of these little ant-like thingies.
Solder the Diodes Onto the Board
Place each of the diodes back into the board. Its important to check the orientation of the diode. It has a black line on one side which lines up with the line on the PCB. (See image)
Getting the diodes into place is kind of fiddly which is why I said if you have snips that will let you cut the legs flush without removing them, it will make your life easier. I didn't have that so I used tweezers to place them back in, which helped a bit.
Solder each of the diodes into place.
Prepare the LEDs
Push the LEDs through the board and then cut the legs off. Just like with the diodes; its important to push the legs through the board first, to get them spread to the correct angles, before cutting the legs.
There's a bit of trial and error with cutting the legs to the right length. If you make them too long they will stick out, but too short and it's difficult to get the LED back in.
Prepare 16 of these little amputated fellas.
Solder the LEDs Onto the Board.
Push all the LEDs back into the board.
The orientation is important again here. One side of the LEDs has a flat edge and this should line up with the flat edge of the circle on the PCB diagram. (See image)
See if the LEDs are pushed in far enough by putting the silicone pad over the board and checking that they don't interfere with the buttons being pushed.
Solder the LEDs onto the board.
Note: It's since been pointed out to me that since it doesn't matter so much if a bit of the legs sticks out on the back, you could just push the LEDs through, solder them at the back, and then cut the legs off.
Sort Out Enough Jumper Cables
Lets talk about the board a bit.
The board is arranged into 4 columns and 4 rows of LEDs/Buttons.
Each of the columns require 2 connections, one for the LED ground and one for the button ground.
Each of the rows require 4 connections, because we need a separate connection for the red, green and blue channels, as well as a connection for the button input.
Here are the cable colours and pin numbers I selected for each of those connections.
Row | What it's for | Cable color | Pin number | PCB Label |
Row 1 | Red | Red | 22 | RED1 |
Green | Green | 23 | GREEN1 | |
Blue | Blue | 30 | BLUE1 | |
Button input | Yellow | 31 | SWITCH1 | |
Row 2 | Red | Red | 24 | RED2 |
Green | Green | 25 | GREEN2 | |
Blue | Blue | 32 | BLUE2 | |
Button input | Yellow | 33 | SWITCH2 | |
Row 3 | Red | Red | 26 | RED3 |
Green | Green | 27 | GREEN3 | |
Blue | Blue | 34 | BLUE3 | |
Button input | Yellow | 35 | SWITCH3 | |
Row 4 | Red | Red | 28 | RED4 |
Green | Green | 29 | GREEN4 | |
Blue | Blue | 36 | BLUE4 | |
Button input | Yellow | 37 | SWITCH4 |
Column | What it's for | Cable color | Pin number | PCB Label |
Col 1 | LED ground | White | 38 | LED-GND-1 |
Button ground | Black | 39 | SWT-GND-1 | |
Col 2 | LED ground | White | 40 | LED-GND-2 |
Button ground | Black | 41 | SWT-GND2 | |
Col 3 | LED ground | White | 42 | LED-GND-3 |
Button ground | Black | 43 | SWT-GND3 | |
Col 4 | LED ground | White | 44 | LED-GND4 |
Button ground | Black | 45 | SWT-GND4 |
Prepare the Jumper Cables
Each jumper cable needs one male end, and one end that is stripped of a few mm of wire.
I like to use some kind of container to capture stripped wire bits as otherwise they end up all over my flat and its possibly worse than glitter.
Solder the Jumper Cables to the Board and Plug Them In
Use the chart from a couple of steps back to get the cables soldered to the correct places on the PCB, and plugged in to the correct pins on the Arduino.
Build Done!
Take a small moment to celebratorily push some (as yet nonfunctional) buttons and then lets get onto some code!
Schematic.
This is a schematic of the PCB and the stuff we've soldered to it.
The grey boxes each represent one of the button / LED combos.
If this looks super complicated (it did to me the first time I saw it) then don't worry, I'm going to break it down.
If you just want to look through the code yourself, its on my github here.
Just the Buttons
The LEDs and the buttons are actually separate from each other (aside from all being connected to the Arduino) so lets just look at the buttons first.
Each grey box contains one button and a diode (the ones we soldered on - I'll explain the purpose of those in a bit).
Note: I'm sure this is super obvious to some people, but I wasn't sure of it when I first started figuring this out so I'll say it! The rows (in green) and the columns (in blue) are not connected, they're just laid across each other. Stuff is only connected where there is a small black dot. Closing one of the button switches however, does create a connection between the row and column.
Set Up Button Pins
For the buttons, we are going to use the columns as outputs and the rows as inputs.
We'll be able to check if a button is pushed because if there's a connection between a row and column then the voltage from the output will reach the input.
To start, in the setup() we output a high voltage to all of the columns.
We set the rows to be pull up inputs which means that by default they also read high.
Scanning
In the loop, a function called scan() goes through one column at a time and sets its voltage to be low.
Then it looks at each button connection row, to see if any of them are reading low.
If a button row reads low, then that means the button that connects that row and column has been pushed.
Not All Button Pushes Are Created Equal
If the button is pushed quickly and firmly then the voltage transfer from the column to the row will be nice and clean.
However, if it's pushed a bit slowly or wonkily, then the voltage might jitter a bit until there is a good connection between the button pad and the contacts on the PCB.
This means that a button push that a human thinks is just one, might be interpreted by the arduino as several separate pushes.
Debouncing
To account for this potential glitch, we use a technique called debouncing.
Every button has a counter (they are held in an array).
When the scan finds that a button is being pushed, we increment that counter. Once the counter reaches a certain threshold, we can consider the button to be really truly pushed. Around 3 or 4 scans is enough for this hardware.
Have a look at the attached gif to see this happening.
Really Truly Pushed - LED on and Message Sent
Once the button is really truly pushed we want to turn on the LED and send a message to the Processing sketch with this button's number.
Each LED has a boolean value (stored in an array) to say whether it should be on or off, so this gets set to true.
(We actually turn the LED on in a bit of code we'll look at in a few steps. You might be wondering why we don't just turn the LED on here. Well, it's because I want the LED to stay on the whole time the button is being pushed, not just flash on and off at the moment it first becomes pushed.)
To write the message to the serial port, we need to calculate the button number from the row and column. There is a simple formula to do that, which you can see in the image.
Letting Go
If a row's voltage is high, then there is no connection between the row and the current column, so that button is not being pushed.
We check to see what its counter is at. If it's at 0 then nothing happens at all - that button isn't being pushed and it wasn't pushed recently.
If the counter is above 0, then we start to decrease it. In the same way that we used a debounce counter to check if a button is really truly pushed, we now need to make sure it's really truly let go of. The scan() function will loop through a few times, reducing the counter until it's at 0.
In the loop that the counter rolls from 1 to 0, then we turn the corresponding LED's boolean to false, so it will no longer get lit up.
The End of the Loop
The scan() loop also contains some code for lighting up the LEDs but we're just concentrating on the buttons at the moment, so lets skip past it for now.
At the end of the scan() loop, the voltage for the current column is set to high again, and the current column number is incremented.
Wait, So What Are the Diodes For?
If we didn't have the diodes, then when two buttons in the same row are pressed, the low voltage from the current column could become connected to the high voltage from another column and cause a conflict.
The diodes control the direction of the current, which stops the voltages from separate columns from meeting and conflicting.
Onto the LEDs
Now that we've seen how matrix scanning is working, let's go back to the full schematic and think about the LEDs.
In the highlighted bits of code in the image, we set up the pin numbers and modes for the LED pins.
If you look at the diagram you can see how the rows consist of a red, green and blue one for each row of LEDs. The column consist of just one ground pin for each column.
We are only going to use one colour, so we don't need to set up all three RGB rows, I'm just using the green rows.
Remember how, to use the buttons, we set the rows to inputs and the columns to outputs, so we could test if a voltage outputted to the column reached the row.
The LEDs work slightly differently. We set the rows and columns both to outputs. By default we output a low voltage to the rows, and a high voltage to the columns. This causes the LED's to all be off. They will light up if we output a high voltage to the row, and a low one to the column.
Scanning the LEDs
Lets return to the start of the scan() function and go through the LED related stuff that we skipped before.
At the start of the function, the LED column's voltage is set to low (as well as the button column's).
When we looped through the buttons, if any were pushed then we set a boolean variable for the corresponding LED to be true.
After the button loop is done, we loop through the LEDs to find which ones are set to true. When we find one, that row's voltage is set to high. This causes the LED to light up.
Then there is a very short (1 millisecond) delay. This gives the LED a moment to shine. (Without this delay the LED will still appear lit up, it just wont be as bright.)
Then all the LEDs are turned off by setting all the rows to low voltage.
Even though the LED is turned on and off every time the scan() function loops through, this is happening so fast that to the human eye it just appears as if the LED is on continuously as long as the button is pushed.
The End of the Loop (again)
Now we're back at the end of the loop. The column voltages get set back to high and we increment the column counter.
Now we've looked at everything in the Arduino code!
Push Some Buttons and See Them Light Up!!!
Now when we press the buttons, the LEDs light up! Hurray!
Have a look at the gif to see.
There's also a serial message being sent down the USB cable, but its going nowhere. Lets get on to some Processing code, pick up those messages and do some cool stuff with them.
On to the Processing Code
Our goal is to create a bunch of spinning cubes on the Processing canvas.
To do that, we're going to create a cube class and a lot of instances of that class (objects). A class is like a cookie cutter, and an object is a cookie you make with that cutter. We're going to store the cubes in an array.
If you want to understand more about arrays or object orientated programming, there is an intro to arrays here and an intro to OOP here.
If you just want to look through the code yourself, it's on my github here.
Cube Class: Variables
In the attached image you can see the variables that the cube class has.
There look like loads of variables because most of the variables come in sets. Instead of just 'size', we have 'size' and 'newSize'.
This is so that, when we change a variable, we don't just immediately jump to using the new version. If all the cubes were 100 pixels wide and suddenly jumped to being 110 pixels wide, it would look kind of jerky. Instead, we animate towards the new size. We'll see exactly how in a bit.
Essentially, we have variables to control the location, the size, the thickness (the strokeWeight), the hue and the spin (rotation).
We also have variables that says which column and row this cube is in.
Cube Class: Constructor
Because all our cubes start off with the same size, thickness, colour and spin, we just set those to specific values inside the constructor.
Each cube has a different location though. The location is determined by which column and row this cube is in.
In the main setup(), we calculate the gap between each cube by dividing the width by the number of columns (+1, because there are more gaps than cubes), and dividing the height by the number of rows (+1).
Then, when the array of cubes is created in setup(), we assign each one a column and row position.
The cube constructor uses the column and row position, as well as the gaps sizes between columns and rows to calculate the location of the centre of the cube.
Update the Cubes
In the draw() loop, we go through each cube and call functions to update and display it.
The update() function inside the cube class updates the spin and the hue by a tiny bit. This causes the cubes to fade through colours, and to spin around.
Update With Animation for Variable Changes
The update function also handles what happens when a variable is changed.
Like I mentioned before, we don't just want to suddenly update the size of the cube from 100 pixels to 110 because it would look jerky.
Instead, we calculate the difference between the current size and the size we want it to be. Then we divide that by the number of frames we want it to take to reach the new value (I've been using around 20-25). Then we add that to the current value. In this way we slowly edge towards the new value, creating a smooth animation.
Drawing the Cube
To draw the cube, we set the stroke colour and thickness according to that cube's variables.
Then we translate and rotate the canvas to the correct spot and draw a box using the size variable.
Serial Communication
With everything that we've looked at so far, we have created an array of 100 cubes, arranged in 10 rows and 10 columns. Every frame they spin a little bit and update their hue a little bit, cycling through the whole rainbow.
That's great, but we want to control them!
In the attached image you can see how the serial communication is set up.
Receiving a Serial Message
The function serialEvent() runs whenever a serial message is received. It reads in the message as a byte of information and saves it to an int. That int directly refers to the number of the button which was pushed, 0-15.
Depending on which button was pushed, different variables are changed. This function is quite long and you'll probably want to go and look at it properly from the github code, but I'll talk through the different types of variables we're updating.
Changing Variables That Apply to All Cubes
Some of the variables are applied to all of the cubes - the size, the thickness and the spin speed.
Let's take a look at what's happening with buttons 11 and 15, which control the size of the cubes.
When button 11 is pressed, we first check that the size of the cubes isn't already too big (I've set 500px as the limit). Then we go through each one and add 30 pixels to it.
Similarly, when button 15 is pressed, we first check that the size of the cubes isn't already too small, so that it can't go below 0. Then we go through each one and subtract 30 from it.
Changing Offsets
Some variables are offsets. These ones affect the cubes which are later in the array more than the ones earlier in the array.
In the first image you can see how the total offset for hue or spin is increased or decreased when a button is pressed. Then, when the loop goes through each cube to update the variable, the offset value is multiplied by that cube's ID in the array.
This means that:
- the offset for cube 0 is 0
- the offset for cube 1 is a small value
- the offset for cube 100 is a large value.
These are used for hue and spin, creating rainbows and more varied patterns.
Changing the Rows and Columns
Remember how we calculated the location to draw the cube based on the number of columns and rows? Well that means that when we change the number of rows and columns, we have to recalculate the location of every cube.
First, we recalculate the width and height gaps based on the new numbers. Then we use those values to recalculate the x and y values for each cube.
Also note how, in the draw() function, there is a check to make sure we are only actually drawing the cubes which are within the current col/row limits. This stops us from drawing lots of unnecessary cubes off the edge of the screen. This might help our frame rate but also is particularly important when the cubes get big, as we might easily see the edge of an unwanted cube on the screen, even if its centre is outside the canvas.
Changing the Background
The last variable we can control is a simple and fun one - the background opacity.
In every frame, at the start of the draw() function, I'm drawing a large black rectangle, far into the distance on the z axis.
If the opacity of this background rectangle is high, then we only see the cubes as they were drawn this frame.
If the opacity of this background rectangle is low, then we can see a bit of the cubes as they were drawn in previous frames.
This appears as trails!
Note also how there are if statements which determine by how much we change the opacity with each button push. This is because there is quite a big visual difference between an opacity of 0.5 and an opacity of 1.5, but a very very small visual difference between an opacity of 11 and one of 12. (This methodology is also used in the spin speed control)
We're Done!
I hope this was helpful for you and you learnt something!
Future Expansions:
New animations:
Remember with the Processing side of things, this is just one example that I came up with. I've been amazed at the amount of variety of visuals I've got out of this one simple array of cubes (check out the attached images!) You could create any kind of Processing sketch with whatever variables you like and control them with your button pad. I would love to see what you come up with!
Potentiometers:
To be honest, a lot of the variables in my Processing sketch would be better off being controlled by twirly knobs instead of buttons. I just liked the idea of playing with these lovely squishy LED filled buttons, and this is where I ended up. But I'm going to see about adding some potentiometers in the future.
Housing:
I mentioned at the start that Sparkfun sells stuff to house the button pad but I haven't used it. I want to create something bespoke that would house buttons as well as potentiometers.