This week I made a ring display. Why?
Before I get into the juicy details about the LED ring display I made, let's get some background information out of the way.
To create a simple ring LED display, I wanted to use 12 LEDs. (Perfect for a clock!) While that's fine and dandy, 12 LEDs means 12 outputs and 12 resistors; that just isn't going to cut it on the small footprint of my boards. In lecture this past week, we learned about something called Charlieplexing. It allows for more LEDs using fewer pins and resistors. Perfect! Here's how it works:
First, you hook two LED's up to the same pins, with one of them reversed. Since LEDs are diodes, current will only flow through one direction. When neither of the pins are on, no current is flowing, so neither LED is on.
If we only turn on one of the pins, here pin B, then we have current flowing from B to A. This current will get blocked by L1, but flow through L2, causing it to shine!
Similarly, by turning on pin A and leaving pin B off, current will flow through L1.
However, if both A and B are turned on, then there's no net current and neither LED will light.
Well, this isn't that useful because we're still using two pins for two LEDs. However, if we scale it up, we can get much more out of it. By arranging the LEDs in an array, it looks like we can get N² LEDs using only N pins!
Not so fast! If we look, some of the pins intersect with themselves. For example, in the top left corner, the LED is wired to pin A on both ends. If both pins provide the same output (as in the earlier example), then the LED won't light. So that means any LEDs on the intersections are useless.
This means we get N*(N-1) LEDs using N pins. That's still a lot! With those LEDs gone, we get an effective array that looks like this:
Now you might be thinking, "this is fine, but aren't we missing something? Don't LEDs need resistors?" And you'd be totally right. So first, we only have to add in a resistor at each pin. (Don't believe me? Look at this example, A->B = A->Resistor->LED->Resistor->B
. Trace it on the next schematic!) If you noticed, we have 12 LEDs as well (using our math from before, N=4 pins, 4*(4-1)=12
); exactly as many as we need for our display.
Alright, we've got enough LEDs, we've got our resistors, but this is still a square! Making it a circle is pretty straightforward, we just move all of the LEDs into a circular pattern. Here, I've colored the nodes of the LEDs to match the pins they're connected to.
Sweet, all that's left to do is wire it up! (This is pretty easy when I'm just drawing, but it's a real pain to do on an actual board in Eagle.)
So, how do we get this in Eagle, anyways? The Charlieplexed array is exactly as we drew it, no problem at all!
That's easy enough, now it's just up to arranging these in a circle. If only there was an easy way to do that...
(Spoiler, there is)
Luckily for us, Eagle has an "Element array placement" tool that we can use. You can access it via Tools->Element array placement...
in the menu.
The first menu that pops up is for Rectangular Array
. We don't want that, so click on the Circular Array
tab.
In this menu, there are a bunch of different options. Here's an example:
But what do each of these mean? Let's get into the details:
OK
, and you're all set! If you click Help
, you're presented with a description of how this works. Well explained, so it can be useful if you ever forget.
With all of this out of the way, now it's just down to drawing the board, placing components, and figuring out how to wire everything up.
Before we get started, let's take a look at what happens if we take a naive approach. Rather than using Charlieplexing, what would happen if we just used one LED per pin? This:
While that routing is pretty simple, this design requires 8 more resistors and 8 more pins on a microcontroller. Yikes!
Instead of doing that, let's take the principles we learned and put them to good use: (Download here)
Whoah! That board is a little to complex to read like this. Let's look at each side.
The top is much simpler in terms of components, but pretty crazy in terms of routing. I used the technique described above to place the LEDs in a circle.
As usual, I used the Othermill since the alignment plate makes double-sided boards a breeze. Not as usual, the debris from the mill was building up as it cut the outline of the board material. While not so big of a deal, once the job completed I realized that the mill hadn't cut all the way through. You can see the bottom copper layer in the second image. Something went wrong with the calibration of the end mill, such as a tiny speck of debris between the end mill and the calibration plate, that caused it to not go all the way down.
I also learned this time that you're supposed to run holes on both sides of the board. I didn't know that (despite having done multiple holed boards already), and had to spend some time with tweezers poking out the holes before I could rivet them.
And here's what the board looks like with all of the components in place:
The first step to testing the board was to guarantee that all of the connections were correct. I did so by using a power supply and running current from a "row" to a "column" (these are still Charlieplexed), and seeing which LEDs light up. In a correct setup, only one LED would light at a time. In these images, I'm keeping the ground consistent and moving the voltage source to different positions.
Unfortunately, not all of the rows and columns behaved like this. Sometimes, two LEDs would light at the same time while others wouldn't light at all because the "rows" were connected. I was able to sniff these out, and they were due to the mill not completing a full path. I had to do a substantial amount of post-processing to clean it up. There was one trace I didn't catch until I was actually running code, which really sucked.
Speaking of code, I was also able to test the circuit by slightly modifying Neil's Charlieplexing example. Since the principles of Charlieplexing remained the same, all I had to do was change some pins to get it to work. By coincidence, the way I set up the LEDs meant that they were in the same order as Neil's code! But enough testing, let's get into the real code.
This is the final version of the code.
There are quite a few considerations and iterations that went into this final product, but let's just look at what's in this code right now.
The 20MHz resonator is actually there for serial communication, but we can use it for some fast timing as well. To keep track of time, I have an interrupt running. How fast, you might ask? Let's calculate it:
millis
every 25 interrupt calls (I'm not using Arduino libraries, so no name conflicts).
An important feature is being able to tell the display to update. I initially tried to use pieces from Neil's serial interrupt example, but I just couldn't get it to work. I assume that there were clashes with the two interrupts and timings. Because I didn't include a TX pin, I couldn't verify what was going on. Instead, I used Neil's serial polling example. Since it just wastes time in a while loop checking a pin, it worked fairly well. The 115200 baud rate was very inconsistent, so I opted for 9600 instead with a bit_delay_time
of 101 μs
. Slightly higher delays, such as 106 μs
, would frequently lose bits of data.
This example uses serial, but I plan on using I²C for communication later on, so I'll have a two-way communication stream.
Starting out, the LEDs had two values: on, or off. But we can do better than that. Using a PWM of sorts, we're able to get a range of values. But how fast is that? Let's do some more math:
It's important to know what connects to what, right? Take a look:
The ring display is what we made this week, but why are the other two in play?
Alright, enough background info, let's really get into the meat of this week: what can the display display? Why don't I just show you?
A lot happened here, let's break it down:
This website was created by Harrison Allen for How to Make (Almost) Anything at MIT in 2019