A trip through the new project in pictures:
I was late in getting started on the project due to putting finished touches on the office, but I ended up with this wonderful new workbench. Quite the step up from the old door on top of shelf units that I've used for the last 35 years or so (really).
Left to right, we have the Tektronix dual-channel 20MHz oscilloscope (thank you eBay), a bench power supply, a perfboard with sockets on it in front of my venerable blue toolbox (also 35+ years old), a outlet strip with a power supply plugged into it, a perfboard, a STK500 microcontroller programmer, a weller soldering staioin, and a fluke voltmeter.
This the project in its first working version. On the far left partially clipped is the power supply. The upper-left part of the prototype board (the white part) has the zero crossing circuit, and the upper-right has a solid-state relay. A brown wire takes the zero-crossing signal to the microcontroller on the development board, and a brown wire takes the signal back to the relay. The Atmel AVR microcontroller that I use comes in a lot of different sizes, so the development board has any different sockets to support the. On the far-right is a white serial line which leads to my laptop - the AVR is programmed over a serial link.
Back to the zero-crossing circuit. To switch AC power, you use a semiconductor device known as a triac. The triac has a weird characteristic - once you turn it on, it stays on until the voltage goes back to zero. That happens 120 times per second, so to implement diming you need to control when you turn on the power for for each half-cycle.
Here's a picture that should make it easier to understand.
The wavy part is the AC sine wave, and the nice square pulse is the zero-crossing signal, which goes high whenever the AC voltage is low enough. The microcontroller gets interrupted when the zero-crossing signal goes high, and then waits a little time until just after the real zero crossing happens.
If it turned the output right at that point, it would stay on for the whole half-cycle, which would means the light was on full bright. If it never turned it on, it would mean the light was (does anybody know the answer? class?) off. If it turned it off halfway in between, the light would be at half brightness. To implement the 32 levels of brightness means dividing the half-cycle into 32 divisions of each area, corresponding to areas of equal power.
(To be better, I should take into account the power/luminance curve of the incandescent bulb that I'm using and use that to figure out what the delays are. Perhaps the next version).
To do this for multiple channels, you end up with code that does the following:
That gives us a set of lights stuck at a given dim level. To animate, you need to change that dim level over time. That is handled at two levels.
The interrupt-level code handles what I call a dim transition. At least, that's what I call it now, since I didn't have a name for it before. We have a vector of current dim levels, one for each channel, and a vector of increments that are added to the current dim vector during each cycle.
So, if we want to slowly dim up channel 1 while keeping all the others constant, we would set dimIncrement to 1 and set the count to 31. 31 cycles later, channel 1 would be at full brightness.
If we want to do a cross-fade, we set two values in the increment vector.
That all happens underneath the covers - the main program loop doesn't know about it. The main program loop figures out what will happen next after the current dim transition, and then blocks.
My early controllers were all table-based, with the tables pre-computed. This was because I was writing in assembler. The current system could also use that approach, but with only 2K of program memory, the procedural approach is more compact, though it is considerably harder to debug. I have a C# program I use to create and test the animations, but I need to rewrite it to use DirectX because I need a 120Hz frame rate to match what I the dimming hardware does.
To get back to the zero-crossing circuit, I first built this circuit using a wall wart with a switching power supply. Such power supplies are small and efficient, but put a lot of noise into the transformer side. I wasted a lot of time on this, and ultimately switched back to a conventional wall wart (from an old Sony discman I used to have) with a linear power supply. Problem solved.
Back to pictures:
Here's the completed controller board.
In the center is the AVR 2313V microcontroller. The U-shape is the solid-state relays that switch the AC for the lights. These are nice little Panasonic AQH2223 relays, which switch 600mA (about 75 watts) (though you can get the in versions that do 150 watts), are tiny, and, most importantly, they're less than $2 each.
Note that these do not have built-in zero-crossing circuits built in. Most solid-state relays do, but you can't use those to do dimming.
The top has the one transistor used to generate the zero-crossing circuit, a 7805 voltage regulator to provide +5V to the circuit, and a few passive components.
Careful viewers will notice that the upper-right socket is empty. That's because it's only a 15-channel controller, but I used 16-pin sockets. The blue wire that holds the AC socket wires on is wire-wrap wire that I had lying around - these are hot-glued down later on. The two black wires provide the rectified voltage (about 15V IIRC) from the wall-wart.
The controller board is in a small plastic box designed to hold 4x6 photos, and then that's inside of a larger box. This lets me keep all of the electrical connections inside of the box. It's not really required for safety, but if you have a lot of exposed plugs and some water on them, you can get enough leakage from them to trip the GFI on your circuit. So having them all inside is better.
The box will be enclosed in a white kitchen garbage bag for weather protection when it's outside. That seems low-tech, but has worked will in all of my controllers over the years.
Projects like this often come down to cabling. Each light needs a wire that goes from the controller out to the light. I did a random layout of lights on the tree, and put them on 5 different ropes so they could easily be pulled up the tree on a pulled.
Here are the 15 lights and all the extension cords required to hook them up. In this case, I used 24 15' extension cords because it was cheaper and easier than building the cables up from scratch.
That's all for now.