1
0
mirror of https://github.com/rfivet/stm32bringup.git synced 2025-09-24 07:14:02 -04:00

Finalize ADC related code and docs.

This commit is contained in:
2025-01-19 15:18:45 +08:00
parent e24be26c20
commit 2ecba6974b
6 changed files with 155 additions and 55 deletions

View File

@@ -39,7 +39,7 @@ The initialization steps:
<li> Configure acquisition direction and mode.
</ul>
<pre>
static void adc_init( void) {
const unsigned short *adc_init( unsigned channels) {
/* Enable ADC peripheral */
RCC_APB2ENR |= RCC_APB2ENR_ADCEN ;
/* Setup ADC sampling clock */
@@ -64,13 +64,16 @@ static void adc_init( void) {
} while( !( ADC_ISR & ADC_ISR_ADRDY)) ;
/* Select inputs and precision */
ADC_CHSELR = 3 << 16 ; /* Channel 16: temperature, Channel 17: Vrefint */
ADC_CCR |= ADC_CCR_TSEN | ADC_CCR_VREFEN ;
ADC_CHSELR = channels ;
ADC_CCR |= (channels & (1 << 17)) << 5 ; /* VREFEN */
ADC_CCR |= (channels & (1 << 16)) << 7 ; /* TSEN */
ADC_SMPR = 7 ;
/* Select acquisition direction and mode */
/* Default scan direction (00) is Temperature before Voltage */
// ADC_CFGR1 &= ~ADC_CFGR1_SCANDIR ; /* Default 0 is low to high */
ADC_CFGR1 |= ADC_CFGR1_DISCEN ; /* Enable Discontinuous mode */
return TS_CAL1 ;
}
</pre>
@@ -88,7 +91,7 @@ command, wait until completion and fetch the result in the ADC Data
Register.
<pre>
static unsigned adc_convert( void) {
unsigned adc_convert( void) {
/* Either only one channel in sequence or Discontinuous mode ON */
ADC_CR |= ADC_CR_ADSTART ; /* Start ADC conversion */
do {} while( ADC_CR & ADC_CR_ADSTART) ; /* Wait for start command cleared */
@@ -196,7 +199,7 @@ calibration value addresses and the following implementation of
<pre>
void adc_vnt( vnt_cmd_t cmd, short *ptrV, short *ptrC) {
if( cmd == VNT_INIT)
adc_init() ;
adc_init( 3 << 16) ; /* 17: V, 16: T */
if( cmd <= VNT_CAL) {
/* Calibration Values */

View File

@@ -15,44 +15,113 @@ potentiometers, sliders, joysticks …
<h2>Voltage Divider Circuit</h2>
<img alt="Voltage Divider Circuit" src="img/39_vdivider.png">
<p>
<b>Vout = Vin * Rref / (Rref + Rmeasured)</b>
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]
<p>
<b>Vout = VADC = VDDA * ADCRAW / 4095</b>
<p>
<b>Vin = VDDA</b>
<p>
<b>~VDDA~ * ADCRAW / 4095 = ~VDDA~ * Rref / (Rref + Rmeasured)</b>
<p>
<b>ADCRAW * Rmeasured = Rref * (4095 ADCRAW)</b>
<p>
<b>Rmeasured = Rref * (4095 ADCRAW) / ADCRAW</b>
<p>
<b>Rmeasured = Rref * (4095 / ADCRAW 1)</b>
in the range [0 … 4095], I can express the two voltages in terms of ADC values.
<br>
&nbsp;<b>Vout = VADC = VDDA * ADCraw / 4095</b><br>
&nbsp;<b>Vin = VDDA</b>
<p>The voltage divider relationship now becomes<br>
&nbsp;<b><i>VDDA</i> * ADCraw / 4095 = <i>VDDA</i> * Rref / (Rref + Rmeasured)</b>
<p>which can be further simplified as<br>
&nbsp;<b>ADCraw * Rmeasured = Rref * (4095 ADCraw)</b>
<p>The resistor value is then given by<br>
&nbsp;<b>Rmeasured = Rref * (4095 ADCraw) / ADCraw</b><br>
or<br>
&nbsp;<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 &lt;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>
<hr>© 2020-2024 Renaud Fivet
<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 &lt;limits.h>
#include &lt;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>