Introduction: MIDI Piano Lighting

About: instagram - @capricorn_one

MIDI controlled light bulb fixture using vintage style "edison" light bulbs.

The control box serves a few purposes, but mainly allows you to change modes:

  • CLASSIC - MIDI notes are mapped to bulbs Cn-Bn mapped to bulbs 1-12, intensity of bulbs is fully on, sustain pedal holds notes of same time, clears all bulbs when released.
  • VELOCITY - same as Classic, but intensity of bulb is controlled by the velocity of the note played.
  • SCROLLING (shown in video) - in this mode, bulbs are lit up sequentially as more notes are played, with intensity of the bulb based on velocity of the note. Releasing the sustain pedal clears the bulbs.
  • AUTOMATIC - Slow moving algorithm that creates a changing visual display in random patterns.

I divided the steps up into three parts :

  1. Hardware (1-3) - Light bulbs, sockets, mounting, etc.
  2. Electronics (4-7) - Explanation of circuitry, how to control light bulb intensity (dimming), midi communication
  3. Software (8-10) - Details into the Arduino firmware, using timers, interrupts and PWM to control the light bulb intensity.

Step 1: Hardware : Light Bulbs and Sockets


I chose to use the smaller socket light bulbs (candelabra base, E12 socket) mostly because I had a bunch laying around from another project. However, the smaller base is less of an eye sore in my opinion, and typically is much cheaper than the standard E-26 base light bulb socket (standard light bulb size). I was able to get these sockets on ebay for about 50 cents a piece, which is a lot cheaper than paying $3-4 for a standard light bulb socket.

When soldering the wires to the sockets, you'll notice the solder joint is covered by shrink tubing. That's very important, especially for the live side of the wire so that you can't accidentally shock yourself when it's plugged in. The shrink tubing also provides some mechanical support so that not all of the weight is on the solder joint. I'll talk about it more in the electrical section, but it's very important that the neutral wires are soldered to the outer shell of the socket, not the other way around. This makes it significantly safer for people to be around, in the case that you used exposed metal sockets like I have. The safest solution would be to use insulated sockets, although they will likely be bulkier and more expensive.

Light Bulbs:

The light bulbs come from , although, edison bulbs can be obtained other places as well, including home depot. The important thing is to get a bulb with low wattage. You don't want 12 60 watt bulbs lighting up in your face, even at 25 watts, it can be a little strong sometimes. In addition, the lower wattage makes the electronics and wiring easier because of the lower current requirement.

Step 2: Hardware: Hanging Assembly

The rod that the sockets hang from is just your standard electrical pipe (conduit), you can get this in a wide variety of lengths and diameters at home depot. It's also very easy to cut yourself with a pipe cutter. The best part about it, is that it's dirt cheap. I used a 5 foot pipe that I cut in half, then placed a Tee in the middle to join them, total cost for all the hardware was less than $10.

One nice thing about electrical conduit, is that it's very soft, yet sturdy. So it's easy to cut into, it's easy to drill into, it's easy to shape, but it's also very strong. (And cheap!)

To hang the bulbs, I simply drilled holes in the pipe along one side, about 2 inches apart, then fed the wires through the holes out to the top to the DB25 connector. I decided to cut the wires all the same length and solder them to the DB25 connector first, so that when I went to attach the sockets, I could choose my wire lengths at that time, and have a more visually accurate representation of how it would look.

Step 3: Hardware: DB25 Wiring

Although the light bulbs all share the same neutral wire, I decided to give each bulb it's own pair of wires, all the way back to the electronics box. I could have used a connector with 13 pins on it (12 for the switched line voltage, 1 for the neutral), like a DB15, but that would mean the neutral wire would have to be significantly thicker than the other wires.

At 25 Watts per bulb, each bulb draws ( Watts / Voltage = Current ) -> (25/120) ~= 0.20833 Amps, or about 208mA of current. You can put 200 mA of current through some very small wire gauges without any problem. However, if you were to share the neutral wire, you would have 208mA * 12 = 2.5 Amps of current. Still not a terrible amount, but you would have to use a wire gauge that was thicker for the neutral wire.

Also, because of the way I am hanging my lights, I needed a pretty long cable going from the hanging assembly to the control box, at least about 15 feet. I could obviously make a cable, but that's kind of a pain, instead I figured, if I don't have to worry about the wire gauge, I could just use a DB25 cable and eliminate some very tedious wiring. To keep the wiring simple, I put all the neutral side connections on the bottom row of the DB25 connector, and the LINE side on the top.

Step 4: Electronics : Background

The easiest way to turn a light bulb on and off from a microcontroller is using a solid state relay (SSR). Sharp makes these chips specifically, that can actually handle a good amount of current, and have just about every component inside. Just takes one resistor and you can drive these from a micro-controller pin. That's all well and good for turning the light bulb on and off, but that's where the simplicity ends. Since driving the solid state relay, is really like driving an opto-coupler, which is really just driving an LED, it's logical to think you could control the light bulb intensity by using PWM on the relay, just as you would with an LED. Unfortunately, you can't do this, at least not without doing some other things first.

The difference between a solid state relay, and a basic optocoupler is in the output stage. We need the "opto" part for isolation, so we're not connecting our light bulb circuit directly to our digital circuit for basic safety reasons. However, the output stage of an optocoupler is typically a transistor of some kind (BJT, MOSFET, etc). That will only allow us to pass current in one direction, and a voltage in the other direction would likely damage the part instantly. To control AC devices we need to use a triac. A triac is a type of thyristor which can pass current in both directions, but the important thing to know about triacs is that once they're on, they don't turn off until the current goes below a certain level, close to zero. It's because of this fact that we can't arbitrarily pulse width modulate the LED side of the SSR. In addition, we must be careful to choose a SSR that does NOT have zero-cross detection built in. Some of the SSR's have this feature, to protect the AC device on the other side, but in our case, that will make it impossible to control the triac the way we want. So we need a SSR that has no zero-cross detection, and can handle the current of our light bulb. That last part is pretty easy. The next step is what this is all leading to, in order to control the intensity of the bulb, we have to turn the SSR on and then back off again in one half cycle of the AC waveform. Since the line voltage come in at 60Hz, that means we have 1/120Hz =~ 8.3ms to do this. That's plenty of time for a microcontroller.

The video above demonstrates this happening, the yellow trace is the AC Line voltage, the blue is the PWM signal coming from the microcontroller.

(The box is just a re-purposed switch box that I found at a thrift store. I made cut outs in the back, and use the front panel knobs to change the display modes of the bulbs.)

Step 5: Electronics: Microcontroller - Relays

I had a couple arduinos laying around as well as a custom shield I had made for a different light bulb project, so that's what I ended up using. The shield is really only useful in that it has the optocoupler circuit on it for triggering an interrupt on the arduino when the AC voltage crosses zero. (Zero Cross Detection Circuit). There are quite a few ways to do this, and I happened to have an arduino Mega available, but you could easily do this with a standard arduino as well. You really only need 12 output pins to get it working. If you want the intensity control, you can still do this with standard output pins, but to get the highest resolution intensity, with the quickest code, you need 12 PWM outputs, which the Arduino Uno doesn't have. I have code in the software section for doing this without PWM as well, although you could also implement software PWM and probably be okay on the Uno.

I also happened to have a board from a different project with 12 solid state relays on it, and the necessary resistors. I had to make some slight modifications to it to connect my ground and neutral wires, but the circuit is pretty simple. Each one of the solid state relays needs to be connected to the microcontroller on the LED side, in series with a resistor. Essentially, you're just driving 12 leds as far as you should be concerned. Just make sure that you're connecting to PWM output pins to make your life simpler.

The relays I'm using are Sharp PR22MA11NXPF. They are great because the package is small, they can pass AC current up to 400V! and currents of 150 mA. They are many variations of this package, some can pass currents of over 1000mA if you need higher current applications. I chose to use the low current version as an additional safety concern. If I plugged in anything that could pull over 150mA of current, I may damage my wiring, or myself somewhere else down the line. At least this way, the relay will fry before anything else... hopefully.

(Note: The PR22MA series seems to be discontinued, but the PR33 series is still in production, it's essentially the same part but can have loads of 300mA at up to 600V!)

Step 6: Electronics: Zero-Cross Detection

To do the zero-cross detection, I use an awesome chip made by Vishay, the SFH6206. The two-LED side turns on a transistor on the opposite side, really it's an AC controlled optocoupler. There are many ways to do zero-cross detection, but this way I find to be the simplest and cheapest. You simply buy the smallest 115->12.6V transformer, or something in that range, a 9V or lower would also work. Then, you plug your transformer into the SFH6206 with a resistor to limit the current.

The triac side (AC from transformer) can handle up to +-60mA, but you don't need that much. However, the more current you pass through, the more current is passed on the digital side (transistor sink output), and consequently, the faster response you will have. I chose a 470 ohm resistor in series with my 12.6VAC, which gives me ~ (12.6/470) ~= 27 mA of current. So my transformer only needs to be rated for greater than 27mA, which lets me use a much smaller transformer.

An alternative would be to just plug the AC directly into the SFH6206 and use a bigger resistor. However, if I did this, and I wanted 20 mA of current through the LEDs, I would need 120-1.5(LED forward voltage) = 118.5 / 20mA ~= 6Kohm resistor. However, a 6K resistor, with 20mA going through it is dissipating ~2.5 Watts of power, that's actually kind of a lot, and will generate a substantial amount of heat, while also being very energy inconsiderate :D. I'd much rather not have to worry about leaving my controller box on all the time and use more efficient electronics.

On the output side (transistor sink, open collector) I tie the emitter to ground, and add my own 1K pullup resistor. The reason I don't use the internal pull-up resistor on the microcontroller is that it is much higher, close to 30K. The result is a much slower pulse when the AC near zero volts. As a result, the timing isn't as accurate, and it's an easy fix with only one part to get the zero-cross interrupt to line up better with the AC voltage.

Step 7: Electronics: MIDI

The MIDI interface is very simple. For a detailed explanation of how it works, refer to the midi electrical specification.

Essentially, you're just isolating the UART (serial) transmission. In my case I'm only listening to incoming midi messages, but I really wish I added a MIDI THRU port so that I could still use MIDI out of my keyboard while connected to the lights. If your keyboard doesn't generate any sounds, this will probably be necessary.

The MIDI transmission signal needs to be connected to the UART RX pin of the micronctroller.

In addition, again just because I had one laying around, I added an ethernet shield to the box so that I could transmit my midi messages over the network, or control the light bulbs remotely. The easiest way to do this is to utilize the IpMidi software that's already out there. All you have to do is configure your ethernet shield as a multicast device on the correct port, and now your keyboard will also have a MIDI network bridge. I have quite a few synthesizers hooked up to a different keyboard, and typically only use my piano to play the piano. But I thought it would be nice to be able to have the option to control other equipment if I wanted to. Step 10 goes over how to setup ipMIDI, but it's really a separate thing altogether, so skip over it if you're not interested.

Step 8: Software: Timers and Interrupts

I don't know what pictures to post for explaining the software, so, obviously, probably better to just post a picture with a cat. Also, the full arduino code is available to download above. (lbdOrganPWM.ino)

Timers and Interrupts

In order to use the hardware PWM in sequence with the ac line triggering, all we have to do is make sure the counter that is used to output the PWM value is reset at the zero-crossing of the AC line voltage. With the triggering circuit explained in step 7, we attach a hardware interrupt to this pin to reset the counters when it triggers.

Each timer is initialized at the beginning of the program to the waveform generation mode of Fast PWM mode with ICRn as the top value. This timer mode is what makes using the hardware PWM possible.

Since we have 7-bit resolution in the velocity of the MIDI note, we want to get pretty close to this resolution for the light bulb intensity. Honestly, anything above 16 steps is pretty negligible, but since we can we might as well. So if we set the prescaler of the timers to 1024 (pg. 161 of Atmega2560 Datasheet), then we have 16Mhz / 1024 for our timer clock = 15625Hz. Our goal frequency of our total PWM waveform is 120Hz, to match the half cycle of the 60Hz line voltage. So, we divide 15626/120 to get ~130. That means if we set our ICRn register to130, we will get a frequency matching the line frequency with ~130 steps. That's perfect since we have 128 steps in the MIDI velocity!

After trying this out, there's some variation in the line frequency, and so I ended up using a top value (ICRn) of 122. This seemed like a safe enough value where I would make sure I would turn off the triac at the zero-cross. If you go too far past the zero-cross threshold, you miss it and the light never turns off or dims.

Last thing, about the Timer config is the PWM polarity. Because once the timer gets to it's end point we want to make sure the triac turns off (this means we're close to the zero-cross), we have to use the inverted mode of the PWM pin, so that when the Timer = 0, the pin is low. That just means when we get a velocity value of 100, we really want to set the PWM register to 127-100 = 27.

For handling the zero-cross, the arduino library has a built in function for this using the hardware interrupt:


Where 5 is the interrupt number (pin 18 on the Mega), FALLING indicates it is only triggered on the falling edge (1->0, not 0->1) of the interrupt pin, and zeroCrossDetect is the function below.

It works by setting all the counters used by the specific PWM pins I have chosen to zero at the zero cross. It also sets up the prescaler and wave-form generation mode for each timer, the reason for this is so that the timer starts on the AC cycle at the beginning of the program, ensuring there's no offset from the start.

void zeroCrossDetect() { 

  TCNT1 = 0;
  TCNT3 = 0;
  TCNT4 = 0;
  TCNT5 = 0;
  TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS10) | (1<<CS12);
  TCCR3B = (1<<WGM32) | (1<<WGM33) | (1<<CS30) | (1<<CS32);
  TCCR4B = (1<<WGM42) | (1<<WGM43) | (1<<CS40) | (1<<CS42);
  TCCR5B = (1<<WGM52) | (1<<WGM53) | (1<<CS50) | (1<<CS52);

Step 9: Software: MIDI

I won't spend much time with how the MIDI works, there's plenty of tutorials on that. However, I highly recommend the Arduino MIDI Library found here.

For my needs, I really only need a few of the functions provided by this library. Specifically Note On, Note Off, and Control Change (for recognizing the sustain pedal).

The way I implemented the sustain pedal with the bulbs depends on the mode that the controller is in.

For the "CLASSIC" and "VELOCITY" modes, the sustain pedal keeps any light bulb on of the the same note value until it is released. When it's released, all the bulbs are cleared.

In the "SCROLLING" mode, the sustain pedal is ignored.

Also, when using IpMIDI, program changes and bank changes are recognized, although I haven't completely figured out if I'll use that at all.

Step 10: Software: IpMIDI

This section is really specific to my personal use of the control box. It has very little to do with the light bulbs, but does allow for automation over WIFI.

ipMIDI is a great software module that lets you use your MIDI devices of your local network (LAN).

All you need to do to set this up is configure your ethernet board for sending multicast messages on address, on port : 21928.

Then, when you receive midi messages of any kind, just retransmit them as UDP messages, and as long as you have ipMIDI installed on a network computer somewhere, you should start seeing midi notes coming in. Similarly, if you send out messages to the ipMIDI device on your computer, you can control the light bulbs using standard midi sequencing.

Step 11: Glamour Shots

That's it! Here's some more photos :D

Lamps and Lighting

Second Prize in the
Lamps and Lighting