3.9 Reading a Resistor Value

I used the ADC previously to read the internal sensors, so it’s simple to move on to external ones. Once you can read the value of a resistor, there is a wide choice of analog applications: thermistors, photocells, potentiometers, sliders, joysticks …

Voltage Divider Circuit

Reading a resistor value is based on the voltage divider circuit.
Voltage Divider Circuit
The relationship between the two voltages, Vin and Vout is
 Vout         Vin
────── = ──────────────
 Rref     Rref + Rmeas

ADC Readings

Assuming the ADC is configured for 12 bit precision and return a value in the range [0 … 4095], I can express the two voltages in terms of ADC values.
 Vout = VADC = VDDA * ADCraw / 4095
 Vin = VDDA

The voltage divider relationship now becomes
 VDDA * ADCraw / 4095 = VDDA * Rref / (Rref + Rmeasured)

which can be further simplified as
 ADCraw * Rmeasured = Rref * (4095 – ADCraw)

The resistor value is then given by
 Rmeasured = Rref * (4095 – ADCraw) / ADCraw
or
 Rmeasured = Rref * (4095 / ADCraw – 1)

Devil in the whatnots

Some of the things to pay attention to while coding

● Division by Zero

In the case where ADC readings would return 0, I chose to return INT_MAX as a value to flag the error.
#include <limits.h>
int R = ADCraw ? Rref * (4095 / ADCraw - 1) : INT_MAX ;

● Integer Calculation

As integer based calculation tends to round intermediary results, I use the developped formula
int R = ADCraw ? Rref * 4095 / ADCraw - Rref : INT_MAX ;

● Reference Resistor Selection

The pair of resistors, reference and measured, has to be selected according to the use case. For now I am experimenting with the measurement of a photoresistor and use a reference resistor of 10 kOhm.

● Calibration

For resistor reading, the reference voltage value VDDA does not influence the calculation. From a hardware design point of view, having a stable reference voltage still remains a key factor in securing reliable ADC readings.
For precise resistor value reading, the calibration of the reference resistor will be key. In other cases, the raw ADC reading can be used in combination with dynamic range acquisition.

Sampling Code

I create adcext.c to take readings every second.
#include <limits.h>
#include <stdio.h>
#include "system.h"	/* uptime, yield(), adc_init(), adc_convert() */

#define RREF 10010  /* Rref is 10kOhm, measured @ 10.01 kOhm */

int main( void) {
    unsigned last = 0 ;
    const unsigned short *calp ;        /* TS_CAL, VREFINT_CAL */

/* Initialize ADC and fetch calibration values */
    calp = adc_init( 2 | (1 << 17)) ;   /* ADC read on GPIOA1 and VREF */
    short Vcal = calp[ 1] ;             /* VREFINT_CAL */

    printf( "factory calibration: %u, %u, %u\n", calp[ 1], calp[ 0], calp[5]) ;

    for( ;;)
        if( uptime == last)
            yield() ;
        else {
            short Rsample, Vsample ;

            last = uptime ;
            Vsample = adc_convert() ;
            Rsample = adc_convert() ;
            printf( "%i, %i, %i, ", Vcal, Vsample, Rsample) ;
            int res = Rsample ? RREF * 4095 / Rsample - RREF : INT_MAX ;
            Vsample = 3300 * Vcal / Vsample ;
            printf( "%i, %i.%i\n", res, Vsample / 1000, Vsample % 1000) ;
        }
}
I add the composition in Makefile
SRCS = startup.crc.c adc.c adcext.c

Checkpoint

Building up on the previous ADC reading of internal sensors, sampling of an external resistor pair connected to one of the IO pin is straightforward.


© 2020-2025 Renaud Fivet