LED Touch Sensor

http://web.ndak.net/jdgrotte/touchsensor/MVI_0120.AVI

Has anybody ever done this? Used an LED to sense light, basically use it as a touch panel?  Well, as it turns out, a bunch of other people have done this...but I haven't, so that's what makes it cool in my book.  I think I earn style points.  I was doing some reading in the good book (otherwise known as 'The Art of Electronics') and found a section talking about this, so I figured I'd try it out.  I ended up with a string of 7 IR LEDs ('cause that's all I had on hand), anodes to a common pin, cathodes to different pins on a PIC18F4620, all pins are schmidt trigger types (PortD, PortC, PortA.4, etc. The TTL inputs draw too much current and don't give a decent reading). I've also got an LCD hooked up for a display of whatever was going on.  (Note:  I tried using standard LEDs of various colors, but they didn't seem to work very well for me.  I would've liked to use visible light LEDs to give a neat looking input panel type of thing, but it didn't work.  Oh well).

Jist of the story, the software runs, I've got the LEDs lined up in a row, I run my finger over (but not touching) the IR LEDs, I set up a block on the LCD to basically follow my finger running over the IR LEDs! This is neat.

To read the IR LEDs, set 2 pins as output. You first forward bias the IR LED (one pin high, one pin low) for about 1ms. Next you reverse bias (high goes low, and low goes high) to essentially 'charge' them up for another 1ms or so (every diode has a certain amount of capacitance when reverse biased. Then you set the pin that's high when the LED is reverse biased to an input. Immediately after that you set up a counter inside a tight if/then. When the input changes from a 1 to a 0 (because the voltage 'stored' in the LEDs capacitor is decaying), you save the count and exit the if/then. The count you get when you exit the if/then is inversely proportional to the amount of light hitting the IR LED, the lower the number, the more the light; the higher, the darker. I then compare all of the numbers, looking for the lowest number and display a solid block in that character position.

Here's a snippet of example PBP code (without the generic sections like LCD defines, config settings, etc. oh, and my PIC is running at 40mhz, I don't know how well it'll work at slower speeds, if at all.):

looper var byte : anode var porta.1 'common anode of all IR LEDs
t1 var portc.5 : t2 var portc.4 : t3 var portc.1 : t4 var portc.0 : t5 var porte.2 : t6 var porte.1 : t7 var porta.4 'all IR LEDs cathodes pins

touchval var word[7] : touchmin var word[7] : touchmax var word[7] : touchrange var word[7] : touchpos var word[7] : touchaverage var word[7] : touchmidpoint var word[7] : maxval var byte : maxtemp var word

skipsubs: 'generic setup I use for all my programs, YMMV
flags=0 : pause 1000 : intcon=0 : intcon.7=1 : intcon.6=1 : intcon.5=1 : intcon2=0 : intcon2.2=1 : intco3=0 : pir1=0 : pir2=0 : pie1=0 : pie1.5=1 : pie1.4=1 : pie2=0 : t0con=0 : t0con.7=1 : t0con.6=1
t0con=t0con+2 : t1con=0 : t2con=0 : t3con=0 : ccp1con=0 : ccp2con=0 : pwm1con=0 : eccp1as=0 : sspstat=0 : sspcon1=0 : sspcon2=0 : txsta=0 : txsta.6=0 : txsta.5=1 : txsta.4=0 : txsta.2=1 : rcsta=0
rcsta.7=1 : rcsta.6=0 : rcsta.4=1 : baudcon=0 : baudcon.3=1 : spbrgh=4 : spbrg=16 : adcon0=0 : adcon1=$f : adcon2=$ff : cmcon=7 : cvrcon=0 : hlvdcon=0 : trisa=0 : porta=0 : trisb=0 : portb=0 : trisc=0
portc=0 : trisd=0 : portd=0 : trise=0 : porte=0 : input switch1 : input switch2 : output led1 : led1=1 : led1=0 : pause 200 : led1=1 : pause 200 : led1=0 : pause 200 : led1=1 : pause 200 : led1=0 : pause 200
led1=1 : pause 200 : led1=0 : pause 200 : led1=1 : pause 200 : led1=0 : pause 200 : lcdout $fe,1 : switchignorecount=0 : menu=1 : switchdelay=1000 : output serialdataoutputpin : input serialdatainputpin

touchmin = 65000 : touchmax = 100

loop2:
led1 = counter.0
for looper = 1 to 7 'light an LED, even though you can't see it
    output anode : anode=1 : input t1 : t1=0 : input t2 : t2=0 : input t3 : t3=0 : input t4 : t4=0 : input t5 : t5=0 : input t6 : t6=0 : input t7 : t7=0
    select case looper
        case 1
            output t1 : t1 = 0
        case 2
            output t2 : t2 = 0
        case 3
            output t3 : t3 = 0
        case 4
            output t4 : t4 = 0
        case 5
            output t5 : t5 = 0
        case 6
            output t6 : t6 = 0
        case 7
            output t7 : t7 = 0
    end select
    pause 1 : anode = 0
    select case looper 'reverse bias that same LED
        case 1
            output t1 : t1 = 1
        case 2
            output t2 : t2 = 1
        case 3
            output t3 : t3 = 1
        case 4
            output t4 : t4 = 1
        case 5
            output t5 : t5 = 1
        case 6
            output t6 : t6 = 1
        case 7
            output t7 : t7 = 1
    end select
    pause 1
    select case looper 'switch that LED to an input to read it
        case 1
            input t1
        case 2
            input t2
        case 3
            input t3
        case 4
            input t4
        case 5
            input t5
        case 6
            input t6
        case 7
            input t7
    end select
loop3a:
    'run a counter in a tight loop (loop3a) and keep checking and waiting for the particular LED input to drop from a logic 1 to a logic 0 because the voltage sitting on the LED due to the internal capacitance drops 'slowly'
    touchval[ looper ] = touchval[ looper ] + 1 : if touchval[ looper ] > 65000 then goto kickoutloop3a
    'if we wait too long or the pin is stuck, we'll never get out of the loop
    select case looper
        case 1
            if t1 = 1 then goto loop3a
        case 2
            if t2 = 1 then goto loop3a
        case 3
            if t3 = 1 then goto loop3a
        case 4
            if t4 = 1 then goto loop3a
        case 5
            if t5 = 1 then goto loop3a
        case 6
            if t6 = 1 then goto loop3a
        case 7
            if t7 = 1 then goto loop3a
    end select
kickoutloop3a:
    counter = counter + 1 'just a counter to show a 'heartbeat'
next looper

maxval = 0 : maxtemp = 0 'maxval keeps track of which led had the lowest value, maxtemp keeps track of the lowest value

for looper = 1 to 7 'check al 7 LEDs to find the lowest one
    if touchval[ looper ] > maxtemp then
        maxtemp = touchval[ looper ] : maxval = looper
    endif
next looper

'clear out the line, then display the number of the darkest LED
lcdout $fe,$80," ":lcdout $fe,$80+(7-maxval),DEC1 (7-maxval ) : lcdout $fe,$c0," ":lcdout $fe,$c0+(7-maxval),$ff
for looper = 1 to 7 : touchval[ looper ] = 0 : next looper
goto loop2
END


For some reason, this only seems to work with the IR LEDs I have on hand. That might be because they are all cheap LEDs. Somebody might get this to work with visible light LEDs, I don't know. Then I wonder if this will work with those bargraph LEDs. You could do something silly like those slider thingies on Star Trek where they run their fingers up a panel and the lights follow.
As soon as I get some more ambition, I'll post a quick schematic of how this is all hooked up, but rest assured, it's all very simple:

All LED anodes to one pin (TTL input type, NOT Schmidt trigger)

All LED cathodes to a schmidt trigger I/O port (i.e. on a PIC18F4620, PortD, PortC, some of PortA, etc.)

Normal power, ground, oscillator, etc.  Although I did have to run my PIC18F4620 at 40mhz to get any reliable results.  You might get away with a lower clock speed, maybe with a cleaner circuit, not on a solderless breadboard, that sort of thing.

And a generic LCD display hookup.

Like I said, I'm getting a kick out of this....
Maybe somebody else will too..