Building Avionics for a Rocket Ship Treehouse
OriginsWhen Jon first started talking about building a rocket ship treehouse for his kids, it sounded like a static display: other than the shape and the material, pretty much the same as any other treehouse. But then he started talking about features that would make it come alive: pilot-controlled thrusters! A paint-shaker that simulates the rumble of takeoff! I got excited. I told Jon I thought his plans were desperately lacking something. The quintisenntial artifact that screams "1950's-era fictional space program": lots of flashing lights and dials and buttons and beeps.
One of the things I've been meaning to do for the past few years is improve my working knowledge of electronics. And, fresh off completion of my automatic closet light, I was ready for a bigger challenge. I offered my services as an avionics subcontractor, and Jon readily agreed.
My original idea for the control panel, in April of 2009, was relatively simple: lots of seven segment displays, perhaps 30 of them total, arranged in 6 groups of 5-digit numbers of varying colors. I'd imagined them flashing and counting, a constantly changing sci-fi display of random numbers. As we'll see, I rather dramatically under-estimated the ultimate complexity – and fun – of the project. The avionics ended up being
- a stack of over 10 modular, interconnected boards, that
- collects input from dials, joysticks and a keypad,
- emits output via 7-segment digits and car gauges, and
- controls high-current devices, such as solenoid air-valves, that actuate the rocket's "engines" and "thrusters"
April 2009: The basic design is decided, long-windedlyMy original thought was to do what sounded easiest: get a microcontroller with as many GPIO (general purpose input/output) pins as possible, and attach each segment of each LED to one of them. With my target of 30 digits, each of which has 7 segments plus a decimal point, that's 240 total segments that need controlling. The biggest microcontrollers I could find only had about 100 GPIO pins, so I started thinking of a more complex design: two or three microcontrollers, each controlling half of the digits. My plan was to use "I2C" (a microcontroller communication standard) to have the two controllers talk to each other, in order to synchronize.
Luckily, I ran this plan past my friend Lew before going any further. He knows electronics pretty well, and suggested what I needed were some latches. A '259-model latch is a basic memory device; it can remember 8 bits, and asserts those values on 8 output pins. To change them, you twiddle 5 inputs: first, assert 3 address lines, to tell it which of the 8 outputs you're trying to change; then, assert 1 data line, to tell it what value that output should take; then, "strobe" (i.e., go through an asserted/unasserted cycle) the Enable line.
So, by attaching each of the 8 segments of an LED digit (including decimal point) to the outputs of a latch, it's possible to control all 8 segments using just 5 control lines -- the latch's inputs -- instead of 8. But, more exciting is that this scheme scales well. If I want 24 digits instead of one, I need only 28 GPIO pins: the three address lines and one data line can run on a common bus, shared by every latch. 24 GPIO lines are then needed for each latch's Enable pin, letting us specify which latch we're actually talking to; the rest ignore the address and data lines because they're not being strobed. That is, we can program a latch by asserting the segment number on the three shared address lines (which I'll now call segment select), asserting the value of that segment on the shared data line, and then strobe the one (out of 24) Enable lines corresponding to the digit we're trying to change.
But wait! We can do even better! Another clever device, called a demultiplexer, can reduce our pin requirements even further. '138 series demultiplexers take 3 address lines and one Enable line as input, and have 8 outputs. Unlike a latch, a demux only asserts one of its outputs at a time. When we strobe its Enable line, it strobes whichever of the 8 output lines we've selected using its 3 address inputs. Now, controlling 24 LEDs requires even fewer control lines: we can attach each LED to a '259 latch, with a common bus for the 1-bit data channel and 3-bit segment select, then attach each of the latch's Enable pins to one of the outputs of a demultiplexer. For 24 digits, we'd need 3 demultiplexers, 8 latches on each. Of course, the addressing input lines of the demultiplexers can also be shared. I will now call these shared address lines digit select. Then, connect each of those 3 demux's Enable lines back to the microcontroller. Now we can control each of the 8 segments of 24 LEDs using only 13 GPIO pins: 1 data line, 3 segment select lines, 3 digit select lines, and 3 pins that lead back to the Enable pins of the 3 demultiplexors.
But wait! We're still not done! Now we can add a 2nd layer of demultiplexors. In other words, have one "top-level" demux, and attach each of its 8 outputs to the Enable lines of 8 more demux's. I will call the 3 address lines of this top-level demux the bank select lines. Each of the 2nd-level demux's can control 8 digits, so this is now a total of 64 digits, each with 8 segments (or a total of 512 segments) that can be individually controlled with only 11 output lines on the microcontroller:
1 data lineAwesome! And suddenly I more deeply understand how a computer's memory bus works.
3 segment-select lines
3 digit-select lines
3 bank-select lines
1 master enable line
Unfortunately we're not quite done. When I showed this plan to my friend and colleague JD, he pointed out a problem. The data and segment-select lines have a rather large fanout: the microcontroller asserts just one data line and it is taken as input to all 64 latches. Most parts are spec'd for a maximum fan-out of 10 or 15. So, we also have to add drivers -- basically amplifiers -- one for each of the 8 banks. That is, the microcontroller asserts data and segment-select lines to 8 drivers; each driver re-asserts it to 8 latches.
While the design seemed reasonable, the idea of assembling such a beast did not. Such a board would have hundreds of signal lines and close to 5,000 tiny holes that would need precise drilling. It didn't seem reasonable to try and fabricate it myself. I'd have to learn another new skill: how to draw a schematic and have a PCB professionally manufactured from it.
Board manufacturers charge by the square inch. We wanted the rows of digits to be spread out (leaving room in between for things like labels, dials, etc.), so I added one final feature to the design. Instead of one huge board with all 64 digits on it, I'd make 8 smaller, identical boards, with their 11 magical lines all connected to each other using a ribbon cable. I'd give each board a DIP switch that would let me tell it which "bank number" it was. I should then be able to put a microcontroller on one of the boards -- it doesn't matter which -- and let the ribbon cable distribute its 11 outputs to all the others. Then, we'd be able to manufacture smaller boards in larger quantities, rather than one huge board. Plus, it has the significant advantage of letting us spread the numbers out in the rocket ship a bit more.
With all this decided, I drew up a basic block diagram showing what each board would look like. I was so excited: the project was already paying for itself in teaching me new things!
The diagram above shows the latches, but doesn't depict the LED or resistors attached to each latch. Also, it shows my cheesy board-select method: each output of the first-layer demux is attached to one switch in a bank of 8 DIP switches. Then all the outputs are bussed together. So, to select (say) board 4, you turn on switch 4, and turn all the others off. With some more complex logic, I could have made it work in binary (allowing you to select 256 boards), but this thing was already complex enough and other aspects of the design limited us to 8 boards per stack anyway.
My next task was to pick a microcontroller. The first sub-task was to pick a brand of microcontroller. My only past experience with them was tinkering with some PICs with my friend Guy back almost 15 years ago.
(Aside: We'd tried to build a board we could install in his apartment's front-door intercom so it would unlock the downstairs door if someone pushed the buzzer with a secret morse-code pattern. We hacked together an assembly-code program that seemed to do the right thing on a test board: lighting up an LED for a few seconds when we pushed a button in the right pattern. But it never worked when we actually installed it. Guy's dad, an electrical engineer, suggested a few changed, including "pull-up resistors", which neither of us had heard of at the time and had no Google to help us, so the project fizzled.)I spent a day or two comparing Atmel, PIC, TI and a few others, and finally decided on Atmel. I liked them most of all because they're open: with a gcc-based toolchain and open-source C library the environment felt comfortable. Though I wasn't expecting to write assembly language, the instruction set for Atmel's microcontrollers seemed much easier than PIC's somewhat bizarre and tortured design. The Atmel has 32 registers -- more than my desktop machine has! The PIC is so weird that GCC can't even target it. The PIC's development board seemed a little better (cheaper, and USB rather than serial-port based), but ultimately I decided on Atmel.
The microcontroller I picked was the Atmel Atmega8, an 8-bit microcontroller with about 20 digital I/O pins, as well as some analog-to-digital converters, which will come into play later in the story.
May 12-16, 2009: The first electrons flowBy May, I had a detailed plan in place. However, never having built anything even remotely like this before, the plan might have been completely nonsensical. Before drawing a more detailed schematic, I knew I'd have to do some basic component testing, starting simple and gradually getting more complex. I started ordering parts from DigiKey: the 7-segment LEDs, latches, and so forth.
The last time I'd tried to use an LED was when I was about 15 years old. I was really into electronics back then, but didn't have any good reference texts. One day, I saw a 7-segment LED in Radio Shack and thought "Wow, that would be cool to use!". It seemed simple enough: 7 individual power pins, one for each segment, and one common ground pin. I figured if I just attached a battery across the two, it would light up. I took it home and, unfortunately, couldn't get it to do anything. 20 years later, I started reading about LEDs and learned my problem: I had assumed that an LED was a resistive load, just like a light bulb. It turns out, diodes are magical things that cause a voltage drop but don't provide resistance. If you attach a battery directly across a diode, it's no better than a short circuit: the diode will immediately burn out.
The right design is to add a resistor, chosen so that the current flowing through it, taking into account the voltage drop inherent to the diode, is no more than what the diode says it can take. My plan was to run the system at 5V. The LED data sheet said that the diode drop was 2V. That leaves 3V "visible" to the resistor. My target was about 10 mA per segment so I picked a 300 ohm resistor (actually, 270: the closest value I had in stock).
My first test was simply to attach an LED segment to a 5V power
supply and a resistor, to see if it would light up:
Wow! This was exciting. I'd already done way better than 15-year-old Jeremy!
Next I decided to try out a latch. I attached two segments of the LED to two of the latch's outputs (with resistors in between). Then I attached three buttons to the latch: one to the data input pin, one to the lowest bit of the "segment select" address line, and one to the Enable pin. And I finally learned just how to use pull-up/down resistors: you attach a high-value resistor to, say, a pin and ground. Then, on the pin side of the resistor, attach a push-button tied to Vcc. When the button is not pressed, the resistor (with no current flowing) keeps the pin at ground. When you push it, you tie it to Vcc instead; the resistor is so strong that the connection to ground is overcome.
If I pushed Enable without pushing Data, the segment would go out. If I hold down Data and then push Enable, it lights up. If I did all of this while holding down the Segment Select line, it would happen to segment #2.
I couldn't believe this was all working! I decided the next step was to get the microcontroller to flip the lines automatically, rather than me doing it manually with buttons. I hooked up all 7 segments to resistors and to the latch, and figured out how to use my new microcontroller programming kit. I made a table defining which of the seven segments should be lit up for each number 0-9, and each letter A-Z. (Some letters, like W, X, and Z, can't actually be represented by these displays.) Then I wrote a program having the controller cycle my LED through the all the numbers and letters. It worked!
Next I added a second digit and a second latch, and a first demultiplexor that would let me select which of the two digits I was controlling. The circuit was really starting to take shape now: I had a real data bus, and real segment-select bus, even though there were only two devices on each bus.
May 25-30, 2009: The Second PrototypeI was excited by my success so far but still had a way to go. The ultimate goal was to manufacture a real printed-circuit board, but I didn't feel ready to do that: too many components were still untested, and I didn't quite know what I was doing yet. I wanted to test things like the board-select circuitry and the driver that would amplify the data bus. However, I was running out of space on my little solderless breadboard. Also, as the number of wires went up, it got harder to keep the thing working without a wire popping out or two resistors touching each other. I needed something with more space and that would be more sturdy. I decided to solder together a prototype using a prototyping board.
The first step was to draw a real schematic; this was starting to get too complicated to keep everything in my head reliably.
Next I laid out all the components on the protoboard to get a sense for how they'd all fit together. This required working out a bunch more details, like finding a DIP switch, power connector, a connector for the control bus (and mating ribbon cable and socket), and a socket for the microcontroller (so I could swap it in and out, instead of soldering it permanently). I also switched from the individual wire-lead resistors to IC-shaped resistor chips -- basically just 8 resistors in a 16-pin package.
I soldered the components in, and, following my schematic, started to painstakingly make all the electrical connections. The process of cutting a wire to the right length, stripping the ends, and soldering it in is surprisingly time-consuming. It took many hours over several evenings, after which I'd only fully connected 2 LEDs. I decided there was no point in continuing. A board with 2 digits was complete enough to test what I wanted to test. I'd test with a 2-digit board and let a PCB manufacturer do the hard work of running every wire to a fully populated board.
I turned the thing on and was sad to discover it didn't work. After a while with a voltmeter, I concluded the driver wasn't working properly. Of course, the real problem was that I didn't understand how it was supposed to work. I'd thought that if you put a voltage in (low or high), the same voltage would come out. Not so: it's what's called an "open collector" driver. That means if you put in a low voltage, a low voltage comes out, but if you put in a high voltage, nothing comes out; the circuit is open, and whatever's on the other end just floats. The fix ended up being easy: I attached a pull-up resistor to each of the four outputs of the driver. Then it worked!
Next, I wrote a test program that commands all 64 LEDs in the entire board stack to identify themselves once per second. For example, LED digit 3 on board 5 is told to display "b...5...d...3". This test served two purposes. First, it let me test the board-select circuitry. As seen in the video below, if use the DIP switch to configure the board as Board 2, the right most digit says "b2d0". Then, if I change it to Board 4, it starts to display "b4d0". Cool -- it worked! The test was also useful because it shows the microcontroller (running at 1MHz) has plenty of time to individually program all 512 segments (8 segments x 64 digits) with no visible delay.
June 2009 - Adding a KeypadA few weeks into this project, JD suggested that for the toy to be really fun, the kids should be able to interact with it in some way. It's not very fun to just look at a bunch of random digits endlessly being displayed. To really let the kids imagine they're flying a spaceship, we needed buttons to push and dials to turn! And have those buttons and dials actually do something.
Of course, as soon as he said this, I realized he was right. So I added another new feature: a 16-key membrane keypad, pictured below, that I bought on Ebay for $3 directly from some random electronics house in China. In fact, I bought two of them, since shipping was $4 no matter how many you buy. I'm not sure yet exactly how the toy will actually work; maybe if you push "A" and then punch in a number, a row of digits labelled "A" takes on that value and starts counting up or down. Or something. But, hey, the Apollo missions flew to the Moon using just a keypad and some rows of 7-segment LEDs so I'm sure I can do something at least as awesome.
So, the procedure for scanning the keypad is
- Attach the column wires to microcontroller inputs, with internal pull-up resistors activated. (Internal pull-ups are a really useful feature of many microcontrollers!)
- Attach the row inputs to microcontroller outputs
- Assert low on the first row, and high on the other three.
- Scan each of the four inputs; if any are low, the key in the corresponding column of the first row has been pressed.
- Repeat the previous two steps for the second, third, and fourth rows.
The connector for this keypad had a single-row, 8-pin interface with standard (0.1'') spacing. It so happened that I'd already decided to make the board-to-board control bus a 2-row (16-pin) connector of the same spacing. So I thought I'd do something clever: try to save pins and connectors by plugging the keypad right into the bus connector. Unfortnately, I made a mistake.
The assignment of signal pins to positions on the bus connector is arbitrary. I reasoned that if I assigned the pins carefully, I could just plug the keypad right into the bus connector, and not even use any extra GPIO lines on the microcontroller. I assigned 8 address lines to the first row, and made sure the Enable pin was on the second row. I figured that as long as I kept the Enable pin un-asserted, I would be able to twiddle the other output pins in order to scan the keypad without affecting the state of the latches. I thought this was a clever way to conserve microcontroller IO pins.
I updated the software to periodically switch into keypad-scanning mode: switch 4 of outputs to inputs and scan using the procedure described above. This scanned the keypad perfectly. Unfortunately, what I failed to forsee is that if one of the keys is still being held down when the controller goes back into display-updating mode, the digits don't get programmed properly. The depressed key is now tying together two outputs that might be fighting over keeping their respective lines in different states. Oops!
To fix this, I updated the schematic to bring 4 dedicated input pins out to the first row of the bus connector, and move four of the outputs to the second row. Of course, there was no way I was going to update the physical wiring to test this...the test will have to wait until the prototype PCB arrives.
In the meantime, to test the scanning procedure, I changed the software to display the key pressed for 3 seconds after the key is released. That is, I could press "5" (the display failed while the key is pressed), and after releasing the key see "5" appear on the display for 3 seconds. Yay!
September 6, 2009 -- PCB Layout v1 Complete!I didn't do any work on the project for several months for a combination of reasons: vacation time, work projects, biking from Seattle to Portland. Finally, at the end of August, I picked the project back up -- goaded by Jon who was making phenomenal progress on the physical rocket.
The next step was to try getting a board manufactured. I redrew my schematic using different software, Eagle, that made it easier to go from a schematic to a physical board. I also added a few other features:
- A new power connector -- with a 2-position locking molex connector rather than the screw-in connectors in my proto-board.
- A programming header, so I can program/test/program without physically swapping the chip between the board and the programmer repeatedly. Completely untested of course (I didn't prototype it -- just drew it based on documentation), but we'll see if it works. The most likely-to-fail thing about the programming header is the reset pin. A pin of the microcontroller, /RESET, has been tied directly to VCC in my prototypes. However, the programming board needs to do something funky with /RESET when it programs. So, I tied /RESET to VCC with a 10k pullup resistor, and put the programming board on the microcontroller side of the resistor. Hopefully, this means that when the programming board tries to actually assert either high or low, it will succeed, and when the programming board is disconnected, the resistor will keep the chip in the not-reset state.
- Horizontal bus connector on the edge of the board to make vertical board arrangement easy.
- Two 3-position molex connectors for attaching analog dials as additional inputs. Each connector has 1 VCC (output) pin, 1 GND (output) pin, and 1 pin tied to an ADC (input) pin on the microcontroller. I hope this will let us use analog dials/pots as input, but of course, this is completely untested.
I learned that it takes a lot of tweaking to get the board nice and compact. Early versions were way too spread out. Partly this was because Eagle's default minimum clearance between traces (i.e., the number of "wires" you can pack into a space) was conservative: 1/20'' (50 "mils") between traces. But, the board manufacturers I found claimed they could make boards down to 8 mils; the board got much smaller when I switched Eagle to using a 10 mil routing grid. I also tried to keep the wires from crossing where I had freedom. For example, the resistor ICs are symmetrical; you can go in on the left and come out on the right, or vice-versa. I had each wire enter the resistor on the same side as it exited the latch.
Finally, I ended up with a nice, compact board!
Exciting stuff! I sent this off to a PCB manufacturer in China called OurPCB and should have the boards back in two weeks or so. I'm getting 10 boards for $110. There's a $50 setup fee and $40 shipping; the boards themselves only cost about $2.50 each! Of course, if there were no shipping or setup fee, I'd make one board, see if it needs a correction, then get more. But since the boards themselves are such a small part of the overall cost, it's more cost-efficient to just order them all up-front if there's even a small chance they'll work. So I ordered 10. Keep your fingers crossed!
Jeremy Elson -- jelson, at the domain: gmail dot com