mirror of
https://github.com/rfivet/stm32bringup.git
synced 2025-05-18 13:18:32 -04:00
128 lines
4.2 KiB
HTML
128 lines
4.2 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>3.9 Reading a Resistor Value</title>
|
||
<link type="text/css" rel="stylesheet" href="style.css">
|
||
</head>
|
||
<body>
|
||
<h1>3.9 Reading a Resistor Value</h1>
|
||
|
||
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 …
|
||
|
||
<h2>Voltage Divider Circuit</h2>
|
||
|
||
Reading a resistor value is based on the voltage divider circuit.<br>
|
||
|
||
<img alt="Voltage Divider Circuit" src="img/39_vdivider.png"><br>
|
||
The relationship between the two voltages, <b>Vin</b> and <b>Vout</b> is
|
||
<b><pre>
|
||
Vout Vin
|
||
────── = ──────────────
|
||
Rref Rref + Rmeas
|
||
</pre></b>
|
||
|
||
<h2>ADC Readings</h2>
|
||
|
||
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.
|
||
<br>
|
||
<b>Vout = VADC = VDDA * ADCraw / 4095</b><br>
|
||
<b>Vin = VDDA</b>
|
||
<p>The voltage divider relationship now becomes<br>
|
||
<b><i>VDDA</i> * ADCraw / 4095 = <i>VDDA</i> * Rref / (Rref + Rmeasured)</b>
|
||
<p>which can be further simplified as<br>
|
||
<b>ADCraw * Rmeasured = Rref * (4095 – ADCraw)</b>
|
||
<p>The resistor value is then given by<br>
|
||
<b>Rmeasured = Rref * (4095 – ADCraw) / ADCraw</b><br>
|
||
or<br>
|
||
<b>Rmeasured = Rref * (4095 / ADCraw – 1)</b>
|
||
|
||
<h2>Devil in the whatnots</h2>
|
||
Some of the things to pay attention to while coding
|
||
<ul>
|
||
<li> Avoiding division by zero.
|
||
<li> Integer versus floating point calculation.
|
||
<li> Choosing the reference resistor.
|
||
<li> Calibration of the reference resistor.
|
||
</ul>
|
||
<h3>● Division by Zero</h3>
|
||
In the case where ADC readings would return 0, I chose to return INT_MAX as
|
||
a value to flag the error.
|
||
<pre>
|
||
#include <limits.h>
|
||
int R = ADCraw ? Rref * (4095 / ADCraw - 1) : INT_MAX ;
|
||
</pre>
|
||
|
||
<h3>● Integer Calculation</h3>
|
||
As integer based calculation tends to round intermediary results, I use the
|
||
developped formula
|
||
<pre>int R = ADCraw ? Rref * 4095 / ADCraw - Rref : INT_MAX ;</pre>
|
||
|
||
<h3>● Reference Resistor Selection</h3>
|
||
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.
|
||
|
||
<h3>● Calibration</h3>
|
||
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.<br>
|
||
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.
|
||
|
||
<h2>Sampling Code</h2>
|
||
|
||
I create <b>adcext.c</b> to take readings every second.
|
||
|
||
<pre>
|
||
#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) ;
|
||
}
|
||
}
|
||
</pre>
|
||
|
||
I add the composition in <b>Makefile</b>
|
||
|
||
<pre>SRCS = startup.crc.c adc.c adcext.c</pre>
|
||
|
||
<h2>Checkpoint</h2>
|
||
<p>
|
||
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.
|
||
</p>
|
||
<hr>© 2020-2025 Renaud Fivet
|
||
</body>
|
||
</html>
|