When I was younger, my father used to bring me to see a local stage production of A Christmas Carol every year performed by just 7 actors. It's a really excellent tale, and it's no wonder that it's been told and retold in so many formats and renditions.
The story is a dark and terrifying take on what is otherwise a bright and cheery holiday, and it provides a stark contrast to typical upbeat yuletide fodder. Unfortunately, it's pretty hard to convey a story like this in holiday lights, so I thought, why not just tell the story itself?
Because the book was published over 170 years ago, it has long since entered the public domain, and anyone looking to read a copy can just go here though you can probably find it in hundreds of places online. Living on the first floor of an apartment building, I thought it would be fun to allow passers by to take in a little Dickens if they care to stop for a while. What's especially neat about the book is how incredibly well-known it is. I think that most people could turn to just about any point in the story and know what's going to happen next (even folks who haven't read it).
That's how I came up with this sign. Anyone curious about what the sign is saying can probably identify the book in a matter of seconds and be reminded of this awesome holiday tale. If they like, they can read the whole thing in one sitting (although I hope I don't have too many people hanging outside my apartment doing that).
The end result is a piece of software that controls a "GeekCatch" 3-color LED marquee that I picked up off Amazon for $110. This is certainly not the only thing I plan on doing with the sign, but I thought it was a great way to get acquainted with how it works over the holidays.
The display is an 80x7 array of red and green LEDs that can be combined to produce a few shades of Amber. The refresh rate is a little low which makes it show up funny in video, but it's perfectly readable in real life. It comes with a remote control and a pretty long serial cable for programming. The remote control works, but it has the worst button layout.
Note that the CAPS key is in the middle of the arrows and the enter key is in the top right (it's the one below "Run").
Programming over a serial port is obviously much more convenient. The software isn't beautiful, but it lets you input the text, font, color, and animation style you want, and it sends it out to the display over a serial connection. There is no documentation of this protocol anywhere, but by looping back the serial output into an input using "Free Virtual Serial Ports Configuration Utility" (some freeware app I found online), I was able to reverse engineer it.
GeekCatch LED Marquee Text Protocol
The marquee operates at 9600 baud and sends data over as plain ASCII.
It always starts with a header that looks like this:
- ~128~ is the "address", though I don't know how you'd have multiple marquees on the same bus.
- f01 tells the marquee to store the message in file location 1. The sign can store 99 files, though they all share the same memory, so you can either have one huge file or a bunch of small ones.
After this preamble, you need to specify an animation, color, and font, and speed.
Any text is always prefaced with an uppercase letter which specifies the animation style. The sign has 24 different display styles and a 25th which simply cycles through all of the options. These animations are all designed for text that fits on the screen in its entirety. If all of the text on a single line won't fit on the screen at once, it defaults to a right to left scroll. Text can only have a single animation style per line.
The animations are designated as follows:
|C||Open from right|
|D||Open from left|
|E||Open from center|
|F||Open to center|
|G||Cover from center|
|H||Cover from right|
|I||Cover from left|
|J||Cover to center|
|M||Interlace to center|
|W||Random (pixels are added randomly)|
Color is specified by a backslash followed by a lower case letter. It can be specified anywhere in a line and will apply to all of the text following the marker until the next marker.
|\f||Bright yellow (for some reason, this always shows as red on my display)|
|\i||Layer mix (rainbow)|
|\j||Bright layer mix|
|\l||Saw tooth mix|
|\m||Green on red|
|\n||Red on green|
|\o||Orange on red|
|\p||Yellow on green|
Note that those last four are nauseating and impossible to read.
Similar to colors, fonts can be specified at any point in a block of text. They also use a backslash and a set of lowercase letters. For some reason, a lot of the fonts don't seem to work over serial and just show up as the default font. They all work with the remote control though as far as I've bothered to check.
|\r||5x11 Short-wide (not supported)|
|\t||7x11 Wide (not supported)|
|\v||7x17 Xtra wide (not supported)|
Speed can be specified anywhere in a file and corresponds to the entire file (shared across multiple lines). It specifies the speed of the animations. These are selected with \Y followed by a number 1-8 where higher is slower. If you're trying to display a long string of text that will revert to the default scrolling animation, your only options are really Y8 and Y3. Any other speed doesn't mesh well with the refresh rate of the display and is nearly impossible to read while it's moving. Y8 is painfully slow though, so really Y3 is your only option.
A pause of designated length can also be specified by inserting \Z followed by a number 1-8. Higher indicates a longer pause before moving to the next section of animated text.
Lines, files, and transmissions are all terminated by a different number of carriage return ASCII symbols (ASCII code 0x0D or \x0D in Python).
- Line - One carriage return
- File - Two carriage returns
- Transmission - Three carriage returns
Note that multiple files can be sent by sending "fxx" and a second message after a double carriage return where xx designates the file number.
When multiple files are stored on the display, a previously sent file can be recalled and displayed by issuing the following command:
Where the \x00s represent a null character (0x00) and \x0Ds represent carriage returns. Not exactly sure why so many null characters are required, but it doesn't work without them.
The display does not handle memory overflows gracefully. I found that I could fit about 7485 characters of text into its file buffer until it crashed and just rebooted.
The current temperature can be displayed by sending:
Again, as in the "execute file" section, this command must also be terminated with a bunch of null characters (8 this time). The thing apparently has a thermometer. I never tested it, so I don't know if it's accurate. It's kind of a silly feature for an indoor display.
There are also a bunch of other supported animations, special characters, and clock features. I don't care to look into them right now, but I will update this page in the future if I ever have a need to figure them out.
Custom graphics can be sent to the display as well. Each image is sent with the following format:
A represents the graphic selection. The device has 8 banks A-G where you can send images.
The # symbols are the body of the image. Image data is sent to the display left to right, top to bottom with each bit representing a single pixel of the display. This data is sent four different times each time representing a different color.
The first two times represent red while the second two represent green. This duplication allows you to have two brightness levels of each color as well as multiple different mixing levels for producing orange or yellow colors.
Once the image is stored, it needs to be recalled. This can be done in the body of a typical text message by including the character ^ followed by a letter I through P. Don't ask me why I-P are used to represent A-G, but that's the way it works.
Taking a look inside
This sign has a beeping feature which can be used to assign points in a message where the sign should beep. I have no idea why that's a feature, but there it is. It also emits a beep when it receives a new serial command. This is super super annoying. There's no way to turn it off, so I thought the easier solution was just to remove the source of the beep.
The sign came apart easily once I removed the two screws from either end. The circuit board slid out along a pair of rails and looks more or less exactly as you'd expect.
The front is covered in 10 8x8 LED matrices, but the display is only 7 pixels high. This was fixed by literally blacking out a row of LEDs with a marker.
The hardware probably can't even drive these LEDs, so they were likely blacked out to prevent light from bleeding over from neighboring LEDs.
The back is mostly unremarkable except for a small removable PCB:
The buzzer is easily identified as the black circular thing at the top right. The battery on the top left is likely to keep the internal realtime clock (used for time display purposes) running. I have no idea what the DIP switch is used for.
I might look into this board further in an upcoming project, but for the time being, I wanted to get my display working again, so I removed the buzzer and reassembled the display.
Scrolling a LOT of text
My plan for this display was to scroll a lot of text, and as I said before, the text buffer inside the marquee only supports about 7,000 characters or so.
This of course meant that I had to send the text in sections, but there was a problem: once the display reaches the end of file, it just starts back immediately at the beginning. There's also no way to read back from the display exactly where it is in the file. In other words, I had to find a way to send the subsequent section of text "open loop".
Fortunately, the display uses a fixed-width font. It's not super easy on the eyes, but it does mean that I just need to count how many characters I've sent in order to predict how long it will take to scroll. Through some trial and error, I discovered that it takes almost exactly 137ms per character at speed setting 3 with the default font. I also had to account for how long it took the last character displayed to reach the end of the screen, so I added 15 character's worth of time to each timeout period.
The body of text itself is captured from a text document and "cleaned". I simply made an array of acceptable characters (numbers, letters, uppercase, lowercase, some symbols) and had it only copy over characters that were on that list. This isn't super efficient, but it works. Too lazy to clean up my input text before feeding it in, there are multiple sections where and entire line of text is nothing but formatting symbols (this was captured from a website), so I had to program in a special case for an input string of length zero.
Once the string is cleaned, I add 20 or so blank spaces to the end to allow the text to scroll completely off the display before transmitting new text.
The code is incredibly simple and can be found at the bottom of this page.
I have an upcoming project that requires a large LED matrix. I was originally planning on making one from scratch, but after playing with this thing for a while, I think I'll be sticking with it. The low refresh rate leaves something to be desired, but at $110, it's a bargain and very easy to set up. There are all kinds of crazy things I want to get it to show now. Time? Date? Stock prices? Bitcoin prices? Caller ID? Maybe put one on my bike and use it to say things to rude drivers?
Project files can be downloaded here