Light sensor (Part 2)

Part 1, 2

In the previous part of this publication, I covered the subject of sensitivity. As for many sensor, the range is very broad and a simple configuration cannot help covering the whole range of light intensities.

As in the dated controllers, we might want to use a hand operated sensitivity selector. Although this option works, it is uncomfortable and we would prefer an auto-ranging device which will automatically select the best matching resistor, depending on the actual brightness.
The schematics below illustrate a simple and cheap design which will help covering light intensity from few Lux to hundreds of thousands Lux in four ranges.

The current Ipce flowing through the photo-transistor (U2) passes through the shunt resistor R5. The voltage generated across the resistor is buffered and amplified by U1 configured in non-inverting mode . The voltage divider is made of R4 and one of R1, R2 or R3 resistors which can be individually switched to ground thanks to the CD4066 quad analog switch. Each switch is controlled by a separate line which is wired to the pins from an arduino board. If none from the switches is closed, the amplifier is in unitary gain, so that this design features a 4 stages gain amplifier.
R5 is choosen so that the sensor will not saturate under strong lighting conditions. In my case, a resistor of 100 Ohm was appropriate for the TEPT5600 phototransistor. R1, R2 and R3 where choosen in order to obtain progressive gains of about x10, x100 and x1000. C1 filter the power supply from Arduino: 10 to 100 µF will do the job.

Next is an example of the code which drives this auto-ranging circuit. Starting from the highest sensitivity, the code reads the analog input. If the signal exceeds a predefined level, the code decrements the range counter, switches the next resistor and performs a new reading. The loop breaks when the analog reading lies in the allowed range or when the lowest sensitivity range is reached.

const uint16_t _autoGainTriggerLevel = 1008;
const uint8_t _stages = 4;
/* all switch driver pins must belong to the same port */
volatile uint8_t *_port = &PORTD;
const uint8_t _vStageCtrlMasks[] = {0, (1 << PIND2), (1 << PIND3), (1 << PIND4)};
const float _vGain[] = {1.0, 11.0, 101.0, 1001.0};
const float _responseFactor = 3.0;
const uint16_t _shuntResistor = 100;


void initSwitches(void)
{
    /* set swicthes driving pins */
	for (uint8_t i = 0; i < _stages; i++)
    {
        *(_port - 1) |= _vStageCtrlMasks[i]; 
    }
}


void setSwitch(uint8_t stage)
{
    /* clear swicthes */
 	for (uint8_t i = 0; i < _stages; i++)
    {
        *_port &= ~_vStageCtrlMasks[i]; 
    }
    /* set switch */
    *_port |= _vStageCtrlMasks[stage]; 
    delay(10);
}


void setup(void)
{
	/* initialize serial comm port */
	Serial.begin(38400); 
	/* initialize resistor switches */
    initSwitches();
}


void loop(void) 
{
    uint16_t adcCounts;
    /* find the best range starting for the most sensitive */
    uint8_t stage = _stages;
    do
    {
        stage -= 1;
        setSwitch(stage);
        adcCounts = analogRead(0);
        Serial.print(stage);
        Serial.print(" ; ");
        Serial.print(adcCounts);
        Serial.print(" ; ");
        Serial.print(_vGain[stage]);
        Serial.print(" ; ");
        Serial.print(adcCounts / _vGain[stage]);
        Serial.print(" ; ");
        float lightIntensity = _responseFactor * (float(adcCounts * 10e07) / float(_vGain[stage] * _shuntResistor * 1024 * 7));
        Serial.print(lightIntensity);
        Serial.println();      
    }
    while ((adcCounts > _autoGainTriggerLevel) && (stage > 0));
    Serial.println();      
    /* repeat after delay */
    delay(2000);
}

First we have a couple of declarations: global application related variables and hardware related variables. The response factor relates to the bin of the photo-transistor (as explained before). The _vGain vector contains the gains computed after R1, R2, R3 and R4 ( Gain=(1+(R4/Rx)) ). One function configures the pins wired to the switches and an other sets individual pins (after resetting all switches). The auto-ranging process lies in the loop function. It is almost self-explanatory. Data is printed so that it can be reconstructed as a scatter of x, y data points. Next is a plot of the brightness measured at my desk (on the side which is next to the window): today which is actually a cloudy day.

Leave a Reply

You must be logged in to post a comment.