Moving Sign 2

led_sign-clock

Project completed: 2009
Today my friends Dan and Steve and I at the Hacklab.to (of which I am a member) finally completed a project that’s been on the todo list since the lab opened this past summer. Steve bought a bunch of old LED sign panels and today we completed a sign using 6 of these modules. Each panel is made of 16×32 red pixels about 0.5″ apart. Along the top of the board are 8 power transistors, and the bottom contains a number of logic chips and drivers. The panels are designed to be daisy-chained side by side. There were no docs, pinouts or wiring diagrams with the panels. This article describes how we made 6 of these panels into a completely usable 96×32 (3072 pixels) LED sign controlled by a PC via an Arduino microcontroller board.

First Steps

led_sign_panelAfter a few false starts where we got side-tracked due to work and other projects, this time we finally finished this crazy thing. Steve had bought about 11 surplus LED panels each containing 512 (16 x 32) red LEDs and complete drive electronics. The panels looked in good shape so a few months ago Dan and I sat down on a weekend and figured out how to control one of them. This first involved figuring out the circuitry, which appeared to be a number of different types of logic chips. Having dissected LED panels before, I was surprised to find so many different kinds of ICs inside. It was immediately clear that there were some nifty control ideas going on.

LED signs like this are scanned, meaning that only one row of pixels is on at a time. This enables the use of a small number of drivers to control a much larger number of LEDs. Even though the sign appears to be arranged as 16 x 32 pixels, in actual fact it is addressed as 64 x 8 pixels. The reason for this is that when scanning (multiplexing) LEDs, only one row of LEDs is on at any one time. If the LEDs are to appear at a normal brightness, they need to be driven with more current, seeing as they’re only one 1/8 of the time. So a 20mA LED might be pulsed with 160mA or more. If the duty cycle were much less than 1/8, it might not be possible to deliver enough peak current without damaging the LEDs.

andrew_probingSo the first order of business was to figure out the circuit diagram, or at least most of it, so that we could know how to get the panel scanning, and get pixels to go in. If the scanning gets stuck on a row, it’s quite likely that the LEDs and drivers might be destroyed. Just to be safe in our initial tests we connected a resistor in the LED power supply line to limit the current just in case.

To figure out the circuit we printed out the first pages of the datasheets for the various logic ICs on the board and drew up part of the schematic by ringing out the PCB with a multimeter. This helped us to get a sense of how to select rows, clock pixels in, etc. It turns out that only 4 control lines are needed to control an entire panel, and in fact as many panels (up to some practical limit) as you would like. Without going into too much gory detail, the control lines worked out to be:

  • Mode – There are two distinct modes: setup mode for setting the row and selecting which bank of pixels (there are 4 banks) to control. And pixel mode, which is the mode used for actually loading in the pixels.
  • Clock – The clock is used for loading all the various setup and pixel registers.
  • Data – Actual setup and pixel data is clocked here.
  • Strobe/’OE – Once registers are set up, this line is used to latch data and enable the outputs. (turning on the LEDs)

The basic concept is that you select each of 4 pixel banks (each bank holds 16 pixels) and shift pixel data in. Then you select the row, and turn it on, wait for a bit of time (1mS) and then do the same thing for the next row. Do it over and over and you see an image! With LED signs, you want to scan the entire display at least 100 times per second, or you can see a lot of flickering.

Slightly More Gorey Detail

Ok, so I know some people want to know more details on the control stuff. I’ll try to go through it as best I can with the aid of a sort of half-baked schematic that we made for our notes. (print out for more easy viewing) So, most of the logic circuitry is shown here, with a few exceptions. The pixel and row latch/driver ICs aren’t here because I didn’t feel like making a symbol for them. The RS-422 stuff isn’t here either, but that’s moot. And U79 shows inverted outputs, but they’re not. I just didn’t have the correct symbol for this IC either.

So the meat of the act starts with U86. The mode pin controls this chip which in effect changes around some things in the circuit so that the remaining lines can be used for either setting up the row and pixel banks, or for actually loading in pixel data. We call the modes setup and pixel modes. You can see that low is pixel mode, and high is setup mode.

The strobe pin controls the latching of data in the pixel and row registers/drivers. This line is multi-purpose in that it is connected to both the strobe lines, and the output enable lines. When high, the latches are able to be loaded, when low, the latches are latched and the outputs are turned on. Rather confusing, but also a very nice double-use of a control signal.

The first order of business to setting up a row is to select one of the 4 banks of LEDs to load. What can’t be seen on the schematic are the pixel shift register/latch/driver ICs. These are 8 bit serial shift registers which are arranged in four groups of two. When loading pixels, 16 pixels are shifted for each board. If you have two boards, you shift 32 pixels, starting with the pixels for the farthest panel. This is done four times, since there are four banks of registers like this. So to start, you need to select one of four banks. This is done by going into setup mode, and clocking in the address of the bank into U75. This shift register will then output a code to U46 and U79. These ICs are data selector/multiplexer chips. Only one of the four channels used will be on at a time.

Then we need to shift pixels for the selected bank. Now that the correct bank address is set up in U75, by leaving setup mode we won’t clock it anymore so the address will stay in the register. In pixel mode, we need to shift 16 pixels * number of boards connected. If we shift more than 16, U46 and U86 route the output of the selected pixel bank registers into the next board! Since in setup mode all boards will respond together, the pixel data will shift into subsequent boards into the correct banks. All the while the strobe is high because we don’t want the outputs on yet. Next we select the next bank and do the same thing, loading all the pixel into its registers.

Once all four banks are completely loaded with pixels, it’s time to select which row to display, and then turn it on. You’ll notice that the row driver strobe and data lines are always connected to the input, even when we’re in pixel mode. This is probably to save parts on the boards, but it means that you need to be extra careful not to enable the row until it’s time to show the pixels. Because otherwise the data will get clobbered by any additional shifting. This must be the last step. In setup mode the clocks to the pixel drivers and bank select registers are off, so all the pixels will stay untouched in their registers until it’s time to display them. The row driver is controlled by shifting 8 bits into the row driver with one of the bits set, corresponding to the desired row. Once the strobe is brought low, the entire row is displayed!

This whole process is repeated about 1000 times per second or so, and the display scans and shows the desired pixels!

Speaking of desired pixels… the part that wasn’t discussed, but should be fairly obvious, is that when shifting the pixel data, the data needs to come from somewhere. In the Arduino we defined a 2D array with the first dimension being the board number, and the second dimension being 64 bytes long. Since each byte can hold 8 pixels, we can store 3072 pixels in 384 bytes. The physical layout of the LED wiring on the panel, and the fact that logically the display is 64×8 but physically it’s 16×32 means that the pixels don’t appear in what seems like a very useful order. By addressing the framebuffer arrays on the Arduino properly, we made it scan from top left to top right, top to bottom like what would be expected. Each board is held in its own 64 byte array. Adding more boards means increasing the first dimension of the framebuffer. The practical limit for the Arduino is probably 12-14 panels based on RAM size, although the scanning speed would probably not be fast enough with this many boards.

Rocking the Arduino

led_arduinoOnce we had the board mapped out, we decided to get some scanning going. The boards interconnect with RS-422 buses. Since the signals travel about 1″ from board to board it seems rather pointless to convert the signal to a differential signal and back again, but oh well. On the first board we tried, it was desirable to control the logic directly without needing four RS-422 transmitters. So we took a Dremel and sawed the RS-422 receiver off. Connecting wires directly to its output pins we were able to get signals right into the board without any hassle.

As an experiment and to support this popular platform, we decided to use an Arduino board to control the sign. This particular one has only 1K of RAM and runs at 16MHz but we figured it would be a good test of this little microcontroller board. Also, since the Arduino has a built-in USB/serial converter, it was rather handy for controlling from a computer.

In our first tests a few months ago we found that the built-in Arduino routines for controlling I/O pins were far too slow to be useful. We did get a single panel to scan, but barely. And when we added serial receiving so that we could control it from a computer we were lucky to get 3fps. Clearly not suitable for controlling 6 boards at a reasonable update frequency.

This time when we revisited this project in an effort to complete it, we did some more research. Dan experimented with raw I/O performance and found that with the built-in digitalWrite() functions, the best he could get was about 280kHz toggling a bit on and off. After figuring out how to get raw access to the I/O port, he was able to get close to 4MHz. Clearly we were getting somewhere! After reimplementing our Arduino code with this faster I/O access we were getting over 120Hz refresh generating scanning output for 6 panels with CPU power to spare.

A lot of people want to see the Arduino code that we used. Although it’s not usable unless you have the weird surplus LED panels that we used, there should be some good ideas that others can learn from and build upon.

USB Serial Protocol

Once we had scanning code working it was time to figure out how to get data into the Arduino in an efficient way. We opted for a byte-stuffed protocol with message framing but no internal format except for the pixel data. Since we intended to use only 1 bit per pixel, we decided to pack 8 pixels into each byte. This meant a best-case packet size of 386 bytes per frame. We hoped to go at 115.2kbps, but so far we haven’t had reliable performance at this speed. Right now we’re using 57.6kbps, yielding a best-case update rate of close to 15fps. At 10fps the system appears to perform quite reliably. Over-running the Arduino serial routines causes bytes to be dropped.

Dan worked on the firmware for the Arduino and I wrote a driver in Java to generate data on the serial port. Because of the hardware layout, pixels don’t shift in to the board in an order that really makes sense from the point of view of a host-side program. We thought we would make the host driver rearrange the pixels before sending them to the Arduino. But we found that it was nearly trivial to do this on the Arduino itself. By swapping a few things around, we were able to support loading one entire physical panel at a time, in a normal left->right/up->down raster format.

bmix Integration

bmixDan and I (with our friend Jonathan) had written the bmix video mixing/routing software for the recent Blinkenlights Stereoscope installation in Toronto. And since the LED board was a similar display format (although much smaller than two office towers filled with lamps) we thought that using the Blinkenlights format might work well for this display. As it turned out (rather by accident, in fact) the finished size of our LED sign was 96×32 pixels, which is exactly the size of the mixing framebuffer used for Stereoscope. This was excellent because there is a lot of content that was created specifically in 96×32 format that we could use for testing. I took my original driver prototype and integrated it with our bmix software, making it an extra output frame type. This only took a little while to add, and magically everything just worked perfectly. In fact, other than adding the new output to send data to the Arduino, I didn’t have to change any of the configuration from what we used at City Hall!

Assembly and Wiring

steev_solderingSteve made great progress on the assembly of the actual panels into a sign with 6 boards all connected together. I was amazed at how fast he had the entire sign completed. Since these boards had been in service before, there were old solder connections which had been hastily removed. Generally most of the boards were undamaged, but still had a lot of old wire and solder on the connections. Steve cleaned all the boards up and then made connections from board to board for the data (4x RS-422 buses) and the power/row control lines. He completed five boards, and once I was done final tweaks to the code, we added the sixth board and tested the entire array.

The first test was the Does it smoke? test. I connected an ammeter and turned it on. No smoke or flames, and idle current of about 1A at 5V. Wow, must be old logic ICs! That’s just the 7400 series logic taking all the power. But nothing seemed shorted or excessively hot. Then it was time to connect the Arduino and see if the code actually worked with six boards. It sort of worked! All the panels were working, but there was a weird problem where other than on the first board, the other boards weren’t displaying the data in the right place. I tracked it down to an off-by-one bug in the firmware. After that was fixed, the entire thing worked perfectly. Amazingly all but four of the 3072 LEDs worked! The bad LEDs had their traces ripped off when being disconnected from an adjacent panel at one point. A bit of wire to repair the traces fixed it in a few minutes.

Final Installation

led_signNow that all the panels were working, it was time to mount them onto something permanent. Steve had brought a piece of plywood exactly cut to fit the panels with some room for the Arduino and power supply at the bottom. He even painted it black. I brought a small Flex-ATX power supply I had from a previous project. We went and got some screws and small rubber standoffs to mount everything. Some eye bolts were used as hooks in the top edge of the board, and we suspended the entire thing from existing eyes in the ceiling over the stairwell. A long USB cable and power cord was used to run back into the server rack. The bmix software runs on a little Mini-ITX server in the rack.

Future Improvements

The biggest drawbacks with the system are based on the limitations of the Arduino. We’d like to replace the serial code with our own library so that we can increase the baud rate. Eventually I’d like to use a faster microcontroller with more RAM so that we can do various brightness levels. Part of the problem with this is that many of the LEDs have fairly bad burn-in, so although they all work, the brightness isn’t consistent between LEDs.

Another feature we intend to add is a beeper. With a small piezo beeper or speaker we could support making short beeps so that people on IRC can send messages to people in the lab on the display. The beep would attract their attention.

It would also be cool to be able to control the brightness of the LEDs. We found that at +5V the brightness is alright for indoors, but not overly bright. At 12V the power consumption is beyond safe values. Strangely enough the board is rated at +15V, but I expect that they are using a different duty cycle. Some more tweaking of the Arduino code could allow us to control and/or increase the brightness.

Conclusion

Every Hacklab needs some cool stuff like this, and I’m glad that we finally found the time to get it all running. The best part of the LED sign is that because it’s being controlled by bmix, there are UDP ports ready and waiting for people to send frames to them. I’m mostly excited to give some basic information about how to send data to the sign, and then see what other people in the lab come up with. I made a clock, and a program to print messages from the command line, but I’m sure there are tons of much cooler ideas that others will come up with.