LEDactus - Generation 2

In this generation we will explore the interesting concept of using LEDs not only as output devices but also as sensors. This is a well known, if little used, principle. If you hook up an LED backwards (reverse bias), it charges up like a small capacitor. If you disconnect the power and immediately measure the voltage across the LED you can watch it decay over a relatively short time. The duration of this decay is partially determined by the amount of light that falls on the LED. The brighter the light, the faster the decay of the voltage. When you touch an LED, you partially cover it and decrease the amount of light falling on it and hence the voltage decays more slowly.

At Least in Theory

Our situation is unfortunately not so simple. Because we use charlieplexing to control our LEDs, it's impossible to isolate a single LED in order to measure its reverse bias decay.

Charlieplexing - 2 banks of 3 LEDs
Figure 1: Charlieplexing two banks of three LEDs

Figure 1 shows a simplified charlieplexing example using 2 banks of 3 LEDs. The I/O lines B0 and B1 are the bank select lines. B2 and B3 are only used to turn LEDs on and off. If we try to test an LED that is turned on and off by a line that is also a bank select line we run into trouble right away.

Testing LED controlled by bank select I/O line
Figure 2: Testing an LED controlled by a bank select I/O line

If we ignore LEDs that are connected to inputs (appear disconnected), the circuit simplifies to that shown in Figure 2. If we try to reverse bias LED1 we are doomed to failure. There is a path through LED6 that will immediately bleed away any charge on LED1 when we switch to measurement mode.

Testing LED controlled by non-bank select I/O line
Figure 3: Testing an LED controlled by a non-bank select I/O line

If we test an LED controlled by a non-bank select I/O line, say B2, the situation looks better (Figure 3). We will be charging LED2 and LED4 and once we switch to measurement mode there is no direct path for the charge to bleed away to ground.

Testing LED controlled by two non-bank select I/O lines
Figure 4: Testing an LED controlled by two non-bank selecting I/O lines

Finally if we choose I/O lines where neither is a bank select line, we end up with the situation in Figure 4. Again we are charging two LEDs, 2 and 4, but we should still be able to measure the decay.

In Practice

Let's start with the simplest situation and see if we can get this to work. A single LED between port A I/O lines A0 and A1.

   Use an LED as a light sensor.
   The voltage across a reverse biased LED decays at differing speeds depending
   on the ambient light.  The voltage across an occluded LED decays more slowly.

void display21(unsigned long pattern){
   static unsigned long lastPattern = 0xffffffff;
   static unsigned short portB_pat[3];
   unsigned short i,j;
   unsigned short *pPortB_pat;

   // to increase performance, the PORTB patterns are cached since they
   // involve the most calculation.  This yields about a 3x increase.

   pPortB_pat = portB_pat;
   if (lastPattern != pattern) {
      lastPattern = pattern;
      for (i=0; i<3; i++) {
         *pPortB_pat = pattern & 0b1111111;
         pattern >>= 7;
         for (j=0; j<=i; j++){
            //roll the pattern left 1
            *pPortB_pat = (((*pPortB_pat) & 0b10000000) > 0) | ((*pPortB_pat) << 1);
  for (i=0; i<3; i++) {
     TRISB = 0b11111111;
     PORTB = *pPortB_pat;
     TRISB = ~(*pPortB_pat | (1 << i));
     // adding a delay after turning on the LED results in a considerably
     // brighter display.  Makes sense.
pPortB_pat++; } TRISB = 0b11111111; }
void main(){
   unsigned long  pattern;
   unsigned long  lastPattern;
   unsigned short portA_read;
   unsigned short i;
   unsigned short j;
   unsigned short k;

   OSCCON = 0x70;       //set the internal OSC to 8MHz (specific to 18F1320)
   ADCON1 = 0b01111111; //turn off analog inputs AN0 - AN6
   INTCON = 0x00;       //disable all interrupts

   lastPattern = 0;
   do {
      pattern = 0;
      // Reverse bias the specified LED
      TRISA = 0b11111100;
      PORTA = 0b00000001;
      // Switch I/O line to input and read value
      TRISA = 0b11111101;      
      for (i=0; i<21; i++) {
         portA_read = PORTA;
         if (portA_read & 1) {
            pattern <<= 1;
         } else {
            // once we read a zero stop building bargraph bit pattern
         // With an 8MHz clock, the display loop takes about 13 msec.
         for (j=100; j>0; j--) {
           // keep displaying the old pattern till the new one is done.
       // wait out the remainder of the 21 delays so update rate is constant
       for (k=i; i<21; i++) {
          for (j=100; j>0; j--) {
       lastPattern = pattern;

  } while (1);

The first thing we do is set the I/O lines A0 and A1 to outputs. By setting A0 high (5V) and A1 low (0V) we are reverse biasing the LED. If the LED glows, you have it in backwards. The charging occurs within one cycle of the PIC chip so there's no need to delay.

TRISA = 0b11111100;     
PORTA = 0b00000001; 	     

So in the very next step we use the TRIS register to change A0 to an input. From this point we will read the I/O line A0, wait a bit, and repeat 21 times.

TRISA = 0b11111101;

As long as A0 returns a 1, we will incrementally add a bit to the long word 'pattern'. But as soon as A0 returns a 0 we will stop adding bits to the pattern and just wait out the time corresponding to the remainder of the 21 test cycles. This will give us a constant test rate.

In the code we use the LEDactus as a circular bar graph by feeding it the generated bit pattern. While we are building the new bit pattern we continue to display the previous pattern.

The Envelope Please ...

Well, it really does work. In an office with fluorescent lighting, it takes about 20-40 msec for the charge to decay on the orange LEDs I use. A red LED I had lying around gave similar results. In near complete darkness, the decay took longer than 273 msec. Intermediate illumination gave intermediate decay times.

Light sensor in office light
Light sensing LED is uncovered. Decay is short (~50msec).
Light sensor partially covered
Light sensing LED is partially covered. Decay is longer (~143msec). Light sensor covered
Light sensing LED is covered. Decay is long (> 273msec)..

Interestingly, fingers don't work on my circuit. I can obscure the LED with an inanimate object like a ruler or a piece of wood and get a slower decay and a larger bar graph display. Fingers brought near the LED (not the PIC board in general) cause the decay to decrease dramatically. Actually, it's simply the wire lead that goes to A0 that is causing this effect. I can reproduce the proximity effect just by insert a piece of wire into the A0 socket with no LED present.

I also tested the following two scenarios between I/O lines A0 and A1. Both respond to the light intensity falling on the reverse biased LED. The other LED has no effect on the decay rate.

Two cases of two LEDs with 1 reverse biased in each case
* reverse biased

The sad reality is, however, that I have been unable to use the LEDs of the LEDactus display to measure light intensity. The decay is uniformly fast regardless of light intensity. Perhaps it's related to the wiring of the display. I may revisit this problem at a later date. I just purchased a RIGOL 100MHz digital scope and may get some insight into what is happening.

Nonetheless, the LEDactus has more fun it can show us. On with the fun.

Next Section: LEDactus - POV Introduction