A few months ago, I found the album Heartland by Owen Pallett on my phone. It was strange that I had no memory of ever purchasing this album, but it was stranger still that I unwittingly carried around what ten years ago would have been a physical CD's worth of music in my pocket for several years as I traveled all over the world. It followed me to China, it followed me to Mt. Rushmore, it was in my pocket on every rainy day and every bus ride, yet I didn't even know it. It's always interesting to look at this little supercomputer in my pocket when a subway or airplane ride disconnects me from the outside world and see how much information I really carry with me at all times. What once took an entire library to store can now fit into a pocket-sized player.
What if I wanted to fit an actual library?
Project Gutenberg is an online collaboration with the goal of obtaining digital copies of all out of copyright works of art and literature. This is an enormous amount of content. Sure, you won't see The Hunger Games there until the copyright expires in a century or so, but many of humanity's great works like Romeo and Juliet, Tarzan of the Apes, Sherlock Holmes, and A Christmas Carol along with tens of thousands of other titles are available to read for free.
In 2010, they released a DVD that contains over 29,500 titles, but the project has grown still since then. 29,500 sounds like a big number, but it's bigger still when you realize that it's not 29,500 books, but 29,500 unique books. Each of these took time to write. Some of them took years. Authors slaved away on each of these titles, and many of them will scarcely be read or even remembered by their decedents centuries later.
In a world where a cheap MP3 player can store 10,000 songs, we often forget exactly what this means. I have 4861 songs in my music library, but 2182 of them haven't been played in the past two years. If I wanted to listen to all of them, it would take me over two weeks of continuous playback. Fourteen days of unique songs that each took weeks or months to write and produce. And my library is pretty small. Some people's libraries would take months.
In an effort to provide context to these kinds of numbers, I created a device that reads books. Day and night, it works its way through the 2010 English Project Gutenberg library paragraph by paragraph displaying each word to anyone who would care to sit and read. In this manner, it will not repeat a book for...
Thirty three years.
But unlike your music library, this device cannot play or pause books at will. Book playback is intrinsically linked to the passage of time, so even when the device is powered off, it continues to read. No matter what you're doing at the moment, this device is reading. You might not see it for a weekend or a few months or a few years but in all of that time as you live and grow it will continue to display unique works of human art every second of every day.
Like some kind of celestial event, each book will only appear every thirty three years. Will you be married next time it comes around? Will you have kids? Will you still be alive?
What follows is a description of exactly how I got this thing working. There were a ton of surprises and revisions along the way, so rather than spoiling it with a schematic outright, you'll have to scroll down to see what exactly I did.
In order to get the effect I was looking for, I needed something that could store a library full of books, display text, and set the time on its own clock. When I first thought of this thing a few years ago, I was planning on building my own LED display, but I quickly gave that up when I realized that you can buy one for fairly cheap. I wasn't sure if an off the shelf display would suit my needs, but as I showed in a proof of concept, a $100 display works just fine if you're careful.
Previously, I controlled the display externally using its included serial cable. Looking a bit closer, I found a MAX232 interface driver inside near the serial port connector. Lifting a few pins and splicing in some wires, I was able to control the display internally with a 5V TTL serial.
The proof of concept provides some details on how I removed the internal speaker (which is totally annoying), but this time around I removed the IR remote control receiver too. I also spliced into the ginormous bypass capacitor near the power connector to provide the 5V power to my own circuit.
There wasn't really a whole lot to do with the display, but I managed to mess it up anyway. Firstly, my BitScope oscilloscope takes 12VDC, but it uses the same size DC barrel jack as the 5V LED display. You can imagine what happened at 3AM. It didn't kill the display, but ...you'll see.
Also, I learned that apparently this thing doesn't do the best job of handling corrupt data packets. At some point while I was working, my board started shooting out random bytes over the serial connection. Somehow, overloading the display's input caused it to permanently corrupt the onboard memory. The display would still receive and show text, but the text was distorted with some characters getting replaced with strange symbols.
The display stores text in non-volatile memory, and somehow this memory was corrupt. There's no way to reformat it, but I thought that maybe if I filled up the entire memory bank with text I'd somehow be able to set everything back to normal. I previously discovered that the display can store about 7,000 characters of text, but trying to send this much data only made things worse until it got to the point where it just reverted to its demo text, and I couldn't do anything.
It was kind of sad actually. I made a few attempts to repair the LEDs before I borked the memory, but apparently the damage was done inside the individual LED matrices themselves where I couldn't get to it.
So, $99 later, I had a replacement. Strangely, the replacement actually had a few cold solder joints on the back when I first got it, so it too had some dead pixels until I could get in there to fix them.
One of the motivations behind this project was trying to find an excuse to learn how to make a large amount of data available to my projects. Previously, I've just stored images and text on a micro controller's internal memory, but after the amount of data required exceeds a few kilobytes, and well before it gets into the gigabytes of the Project Gutenberg library, something better is required.
I got some feedback on the reddit post of one of my old projects two years ago where another user had used an SD card to store image data for a persistence of vision display. At that point, I knew that I'd have to learn to interface with an SD card eventually. I just put it off for a really really long time.
SD cards can be easily accessed with just about any computer, and due to their SPI interface, they can communicate with microcontrollers fairly easily. An SD card isn't super fast when used in this manner, but for what I needed, it was perfect.
Something about the familiarity of a standard push-push SD card socket makes even the jankiest of hobby projects seem so much more legitimate.
The plan was to have this PCB just sort of stick on to the back of the enormous LED PCB and interface with some clever plugs. You might recognize these plugs from here.
Unfortunately, I may have miscalculated the height requirements...
Okay... so less plug and more soldered on wires. Did I say something about jankiness before?
Speaking of, I didn't originally know that SD cards operate at 3.3V, not 5V, but I was able to inline a pretty slick little 3.3V linear regulator to get it working.
Fortunately, the LED display still accepts 3.3V logic on its serial input, so I didn't need to do any complicated level shifting. In the above image, you can see the out of focus blurry resistor divider that I originally had in place to pull the 5V serial from the display down to 3.3V. This was there until I realized that the display never outputs anything over serial, so I removed it.
Wanting to get the "authentic" SD card experience, my first plan was to write my SD card driver myself. I found some details online about communicating with SD cards and learned a few interesting things:
- SD cards have no internal clock, so a lot of the time, you have to send them gibberish just so they can use the incoming SPI clock to do some internal processing and eventually return a value. Putting the card into native operating mode requires holding the MOSI line up and just pulsing the clock for "about 70 cycles".
- In order to read from an SD card, you are required to ask the card if it's an SD or SDHC card even if you already know the answer. You can't read from the card until you ask. My guess is that devices that were made before SDHC wouldn't know to ask and therefore wouldn't mistakenly try to read an incompatible SDHC card.
- There is no easy way to get an desktop OS to write raw data to an SD card that isn't formatted into a filesystem.
I'll spare you the specifics mostly because writing your own SD card driver is an unnecessary undertaking, but also because I did this a few months before writing this blog post, and I forgot most of the specifics. I'll include my code with this post, but I'm not sure if it even runs anymore.
I did manage to get the SD card working, but the biggest problem is interpreting the data coming from the card. The physical layer of the SD card completely ignores the organization of the filesystem (in this case, FAT32). Sure, I can read bytes from the card, but without something to interpret the filesystem, there's no way to open specific files.
Just to make sure my code was working though, I filled the card up with a bunch of text documents and just started reading from a random point. After a few guesses, I found a sector of the card rich with text and got this:
Having both proven my capabilities and inabilities, I moved on to FatFS. FatFS is a generic Windows FAT filesystem interface for embedded devices. I found one sample project on the page that included some "fool proof" code for reading FAT filesystems off SD cards with AVRs. Perfect! The code was fairly easy to configure for my setup.
The code is meant to be more platform independent, so rather than using hardware specific features like the AVR's SPI controller, it bit-bangs the interface on GPIO pins. This is a little upsetting for my application as putting the SD card on the SPI pins causes interference with chip programming which also uses the SPI port. Two times out of three, programming the part will abort prematurely if I don't remove the SD card first. If I had known that I would be bit-banging my solution, I would have laid the board out to connect the SD card to generic GPIO pins of which I have plenty.
Anyway, configuring the software was a breeze, and I was soon reading files off the SD card with ease.