Introduction: These LEDs Are Sensors Too!

About: Finest Bean works with artists and entrepreneurs to create rapid prototypes and interactive experiences.

This is the LED Sensing Matrix — an interactive display I built that responds to light and shadow. Check out the GIF above for one example application of the technology. The moving dot on the matrix seems to "bounce" off a solid object placed on the display.

It's a bit of a hack, because this effect is accomplished without using any dedicated photosensors. Instead, with some clever circuitry, the same LEDS that we use to emit light are configured to detect light as well! No extra components needed.

How does it work? Read on.

Step 1: Controlling Multiple LEDs (Multiplexing)

Controlling a single LED is simple.

  1. Connect the cathode to ground.
  2. Connect the anode to a positive voltage. In this case, we connect it to an output pin on our chosen microcontroller (the Arduino Uno).

When the output is high (positive voltage), the light is on; and when the output is low (zero voltage), the light is off.

But what if we want to control many LEDs?

We could try connecting each anode to a separate output pin on the Uno. However, with a large number of LEDs, we would quickly run out of pins — not to mention electrical power. There is a better solution.

Multiplexing is a technique used to light many LEDs at the same time. The LEDs are arranged in a grid, as shown below. Each column shares a common anode, and each row shares a common cathode. To light an individual LED, say, LED 8, we would connect column 3 to power and row 2 to ground. In this example, we can control 25 individual LEDs using a mere 10 pins.

See Figure 1.

Problem solved? Not quite. Say we want to light only LED 2 and LED 19. To turn on LED 2, we would connect column 2 to power and row 1 to ground. To turn on LED 19, we would connect column 4 to power and row 4 to ground. We would like for only LEDs 2 and 19 to be illuminated. But what we end with is a display like Figure 2.

In Figure 2, LEDs 2 and 19 are illuminated — but so are the LEDs 4 and 17. The problem is that we can’t have a column connected to power in one row, and at the same time, have the column not connected to power in another. To resolve this issue, we connect each row to ground, one at a time, and light the appropriate columns. See the sequence of images below. If we do this fast enough, the human eye can’t notice the flicker of LEDs turning on and off, and it looks like we are lighting all the rows at the same time.

See Figure 3 (GIF).

Step 2: Lighting the Matrix

The Sensing Matrix display is made up of two 8 x 8 dot matrices (Sparkfun COM-00682), which are set up for multiplexing as shown in the diagram below. Each dot contains both a red and a green LED. By turning on only one LED, we can emit either red light or green light. By turning on both of them, we can display yellow light from the dot.

See Figure 1 in Step 2.

Let us first consider only one of the 8 x 8 dot matrices. The 24 pins on the matrix outnumber the available outputs on the Uno, so we need some external device to control the display. For this purpose, we use three 8-bit shift registers (74HC595). Each register has eight digital outputs, similar to the ones on the Uno. To set the outputs high or low, we write an 8-bit word from the Uno to the register. Each bit in the word corresponds to an output on the register, and a 0 or 1 sets the output low or high, respectively.

See Figure 2 in Step 2.

The matrix datasheet tells us that each LED can handle a peak forward current of 30 mA. Go higher than this number and we risk damaging the circuit — go significantly lower and LEDs will appear dim. Given a 5 V power supply, we can restrict the current through a single LED to a safe 28 mA by putting a 180-ohm resistor at each anode. Since we are lighting the matrix one row at a time, each cathode must be capable of sinking enough current to light all 16 LEDs at once — a total of 448 mA.

It’s at this point that we run into a problem: the shift register can’t handle that much current. Each register output can only accommodate 20 mA — short of the 28 mA allotted for the each column and well below the 448 mA needed for each of the rows. Even if the shift register didn’t have this limitation, the Uno itself is only capable of sinking 400 mA. How do we get around this issue?

Instead of using the register outputs to power the LEDs directly, we use them to control series of transistors. The cathode side is connected to ground through an NPN Darlington array (ULN2803), which is capable of sinking up to 500 mA per pin. On the anode side, each column is connected to a 5V power supply through a PNP transistor (2N3906), which can source up to 200 mA. (See image above.) Generally speaking, NPN transistors are better for sinking current and PNP transistors are better for sourcing current — which is why we have them at the cathode and the anode respectively. See the circuit diagram below. (Note: not pictured in this diagram are the 200-ohm resistors between the base of the PNP transistors and the shift register outputs.)

Step 3: An LED As a Light Sensor -- Two Methods of Detection

What makes this project exciting is that we can use the LED matrix to detect light, not just emit it. The catch is that the LEDs can’t be illuminated during the sensing process. This means that sensing has to be done very quickly, so the human eye doesn’t notice that the LEDs have been switched off.

There are two methods of light detection with an LED, described below.

Cathode Sensing.

This technique works by reverse biasing the LED. When the diode is in this state, it acts like a parallel plate capacitor. The diode’s depletion region serves as the insulator, and the anode and cathode act as the charged plates.

See Figure 1 in Step 3.

When light hits the depletion region, it dislodges an electron, creating an electron-hole pair. The electric field then sweeps the hole to the p-side and the electron to n-side — creating a small reverse current from the cathode to anode. If more light hits the depletion region, a larger current is generated.

See Figure 2 in Step 3.

To measure this current, we switch the cathode connection from a 5V output to a digital input. We then measure the amount of time it takes for the input pin to go low. If there is a lot of light, then a large current will be generated and the input pin will fall low very rapidly. If there is only a little light, then a small current will be generated and the input pin will take a longer time to reach low.

See Figure 3 in Step 3.

The advantage of this method is that it doesn’t require any additional hardware — with just two I/O pins on your microcontroller, you can both emit and detect light using an LED. The downside is that detecting light involves waiting for an input to drop low, which can take up to a few seconds in situations with little light.

Unfortunately, this means that cathode sensing and multiplexing are incompatible. For LED multiplexing to be effective, the LED can only be off for a few milliseconds, so the human eye doesn’t notice the flicker. If the LED needs to be off for multiple seconds to detect light, then there will be noticeable flickering.

Anode Sensing.

Anode sensing requires more hardware (and is therefore more expensive) than cathode sensing, but measurements can be taken in microseconds rather than milliseconds or seconds.

The anode is connected to an analog input and the cathode is connected to ground. As in cathode sensing, when light hits the depletion region, it dislodges an electron to create an electron-hole pair. The electron is swept to the n-side and the hole is swept to the p-side. Charge quickly builds, and the voltage difference can be measured by the analog input at the anode. The more light that the LED detects, the larger the voltage will be.

See Figure 4 in Step 3.

With this method, we are able to measure a row of LEDs in mere microseconds — a large improvement from the previous method. Because of the speed of this technique, we will use anode sensing to detect light in the matrix.

The Uno only has six analog inputs, so for this application we use an external analog-to-digital converter (MCP3208). The MCP3208 is a 12-bit, 8-channel ADC that communicates with the Uno using the Serial Peripheral Interface. We connect each channel to an anode in the matrix, and read the values one at a time through pin 12 on the Uno.

Step 4: Final Diagrams