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.
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
- Avoiding division by zero.
- Integer versus floating point calculation.
- Choosing the reference resistor.
- Calibration of the reference resistor.
● 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