mirror of
https://github.com/rfivet/stm32bringup.git
synced 2025-02-21 05:37:09 -05:00
Finalize ADC related code and docs.
This commit is contained in:
parent
e24be26c20
commit
2ecba6974b
3
Makefile
3
Makefile
@ -123,7 +123,8 @@ CRC32SIGN := 1
|
||||
##SRCS = startup.txeie.c adc.c adccalib.c ds18b20.c
|
||||
#SRCS = startup.ram.c txeie.c uptime.1.c
|
||||
#SRCS = startup.crc.c txeie.c uptime.c
|
||||
SRCS = startup.crc.c adc.c adcmain.c
|
||||
#SRCS = startup.crc.c adc.c adcmain.c
|
||||
SRCS = startup.crc.c adc.c adcext.c
|
||||
|
||||
LIBSRCS = printf.c putchar.c puts.c # memset.c memcpy.c
|
||||
ALLSRCS = $(SRCS) $(LIBSRCS)
|
||||
|
75
adc.c
75
adc.c
@ -1,5 +1,5 @@
|
||||
/* adc.c -- system layer
|
||||
** Copyright (c) 2020-2021 Renaud Fivet
|
||||
** Copyright (c) 2020-2025 Renaud Fivet
|
||||
**
|
||||
** ADC for temperature sensor and Vrefint
|
||||
** gpioa low level API and usleep()
|
||||
@ -64,6 +64,8 @@
|
||||
#define RCC_APB2ENR_USART1EN 0x00004000 /* 14: USART1 clock enable */
|
||||
#define RCC_APB2ENR_ADCEN 0x00000200 /* 9: ADC clock enable */
|
||||
|
||||
#define RCC_CFGR2 RCC[ 11] /* 3-0: PLL PREDIV */
|
||||
|
||||
#define RCC_CR2 RCC[ 13]
|
||||
#define RCC_CR2_HSI14ON 0x00000001 /* 1: HSI14 clock enable */
|
||||
#define RCC_CR2_HSI14RDY 0x00000002 /* 2: HSI14 clock ready */
|
||||
@ -126,15 +128,20 @@
|
||||
#define LED_IOP A
|
||||
#define LED_PIN 4
|
||||
#define LED_ON 0
|
||||
/* 8MHz quartz, configure PLL at 28MHz */
|
||||
/* 8MHz quartz, configure PLL at 16MHz */
|
||||
//#define HSE 8000000
|
||||
#define PLL 7
|
||||
//#define HSEPRE 4
|
||||
#define PLL 6
|
||||
#define BAUD 9600
|
||||
//#define HSI14 1
|
||||
|
||||
#ifdef PLL
|
||||
# ifdef HSE
|
||||
# define CLOCK HSE / 2 * PLL
|
||||
# ifdef HSEPRE
|
||||
# define CLOCK HSE / HSEPRE * PLL
|
||||
# else
|
||||
# define CLOCK HSE / 2 * PLL
|
||||
# endif
|
||||
# else /* HSI */
|
||||
# define CLOCK 8000000 / 2 * PLL
|
||||
# endif
|
||||
@ -151,6 +158,12 @@
|
||||
# error clock frequency exceeds 48MHz
|
||||
#endif
|
||||
|
||||
#if CLOCK > 0x1000000
|
||||
# define TICKDIV 8
|
||||
#else
|
||||
# define TICKDIV 1
|
||||
#endif
|
||||
|
||||
#if CLOCK % BAUD
|
||||
# warning baud rate not accurate at that clock frequency
|
||||
#endif
|
||||
@ -225,13 +238,11 @@ void SysTick_Handler( void) {
|
||||
}
|
||||
|
||||
|
||||
void usleep( unsigned usecs) { /* wait at least usec µs */
|
||||
#if CLOCK / 8000000 < 1
|
||||
# error HCLK below 8 MHz
|
||||
#elif CLOCK % 8000000
|
||||
# warning HCLK is not multiple of 8 MHz
|
||||
void usleep( unsigned usecs) { /* wait at least usecs µs */
|
||||
#if CLOCK % (TICKDIV * 1000000)
|
||||
# warning usleep() not accurate at that clock frequency
|
||||
#endif
|
||||
usecs = SYSTICK_CVR - (CLOCK / 8000000 * usecs) ;
|
||||
usecs = SYSTICK_CVR - (CLOCK / TICKDIV / 1000000 * usecs) ;
|
||||
while( SYSTICK_CVR > usecs) ;
|
||||
}
|
||||
|
||||
@ -251,7 +262,7 @@ iolvl_t gpioa_read( int pin) { /* Read level of GPIOA pin */
|
||||
}
|
||||
|
||||
|
||||
static void adc_init( void) {
|
||||
const unsigned short *adc_init( unsigned channels) {
|
||||
/* Enable ADC peripheral */
|
||||
RCC_APB2ENR |= RCC_APB2ENR_ADCEN ;
|
||||
/* Setup ADC sampling clock */
|
||||
@ -260,10 +271,11 @@ static void adc_init( void) {
|
||||
do {} while( !( RCC_CR2 & RCC_CR2_HSI14RDY)) ; /* Wait for stable clock */
|
||||
/* Select HSI14 as sampling clock for ADC */
|
||||
// ADC_CFGR2 &= ~ADC_CFGR2_CKMODE ; /* Default 00 == HSI14 */
|
||||
#else
|
||||
#elif CLOCK <= 28000000
|
||||
/* Select PCLK/2 as sampling clock for ADC */
|
||||
ADC_CFGR2 |= ADC_CFGR2_PCLK2 ; /* 01 PCLK/2 Over default 00 */
|
||||
// ADC_CFGR2 |= ADC_CFGR2_PCLK4 ; /* 10 PCLK/4 Over default 00 */
|
||||
#else
|
||||
ADC_CFGR2 |= ADC_CFGR2_PCLK4 ; /* 10 PCLK/4 Over default 00 */
|
||||
#endif
|
||||
|
||||
/* Calibration */
|
||||
@ -276,15 +288,20 @@ static void adc_init( void) {
|
||||
} while( !( ADC_ISR & ADC_ISR_ADRDY)) ;
|
||||
|
||||
/* Select inputs, precision and scan direction */
|
||||
ADC_CHSELR = 3 << 16 ; /* Channel 16: temperature, Channel 17: Vrefint */
|
||||
ADC_SMPR = 7 ;
|
||||
ADC_CCR |= ADC_CCR_TSEN | ADC_CCR_VREFEN ;
|
||||
ADC_CHSELR = channels ;
|
||||
ADC_SMPR = 7 ; /* 7 is 239.5 ADC clock cycles */
|
||||
ADC_CCR |= (channels & (1 << 17)) << 5 ; /* VREFEN */
|
||||
ADC_CCR |= (channels & (1 << 16)) << 7 ; /* TSEN */
|
||||
|
||||
/* Default scan direction (00) is Temperature before Voltage */
|
||||
// ADC_CFGR1 &= ~ADC_CFGR1_SCANDIR ; /* Default 0 is low to high */
|
||||
ADC_CFGR1 |= ADC_CFGR1_SCANDIR ; /* high to low */
|
||||
ADC_CFGR1 |= ADC_CFGR1_DISCEN ; /* Enable Discontinuous mode */
|
||||
|
||||
return TS_CAL1 ;
|
||||
}
|
||||
|
||||
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 */
|
||||
@ -293,7 +310,7 @@ static unsigned adc_convert( void) {
|
||||
|
||||
void adc_vnt( vnt_cmd_t cmd, short *ptrV, short *ptrC) {
|
||||
if( cmd == VNT_INIT)
|
||||
adc_init() ;
|
||||
adc_init( 3 << 16) ;
|
||||
|
||||
if( cmd <= VNT_CAL) {
|
||||
/* Calibration Values */
|
||||
@ -303,11 +320,11 @@ void adc_vnt( vnt_cmd_t cmd, short *ptrV, short *ptrC) {
|
||||
}
|
||||
|
||||
/* ADC Conversion */
|
||||
*ptrC = adc_convert() ;
|
||||
*ptrV = adc_convert() ;
|
||||
*ptrC = adc_convert() ;
|
||||
|
||||
if( cmd == VNT_VNC) {
|
||||
*ptrC = 300 + (*TS_CAL1 - *ptrC * *VREFINT_CAL / *ptrV) * 10000 / 5336 ;
|
||||
*ptrC = 300 + (*TS_CAL1 - *ptrC * *VREFINT_CAL / *ptrV) * 10000 / 5336 ;
|
||||
*ptrV = 330 * *VREFINT_CAL / *ptrV ;
|
||||
}
|
||||
}
|
||||
@ -326,11 +343,15 @@ int init( void) {
|
||||
#ifdef PLL
|
||||
/* Setup PLL HSx/2 * 6 [24MHz] */
|
||||
/* Default 0: PLL HSI/2 src, PLL MULL * 2 */
|
||||
RCC_CFGR =
|
||||
# ifdef HSE
|
||||
RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_DIV2 |
|
||||
# endif
|
||||
RCC_CFGR = RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_DIV2 |
|
||||
RCC_CFGR_PLLMUL( PLL) ;
|
||||
# ifdef HSEPRE
|
||||
RCC_CFGR2 = HSEPRE - 1 ;
|
||||
# endif
|
||||
# else
|
||||
RCC_CFGR = RCC_CFGR_PLLMUL( PLL) ;
|
||||
# endif
|
||||
RCC_CR |= RCC_CR_PLLON ;
|
||||
do {} while( (RCC_CR & RCC_CR_PLLRDY) == 0) ; /* Wait for PLL */
|
||||
|
||||
@ -349,9 +370,13 @@ int init( void) {
|
||||
#endif
|
||||
|
||||
/* SYSTICK */
|
||||
SYSTICK_RVR = CLOCK / 8 - 1 ; /* HBA / 8 */
|
||||
SYSTICK_RVR = CLOCK / TICKDIV - 1 ; /* HCLK / [8,1] */
|
||||
SYSTICK_CVR = 0 ;
|
||||
SYSTICK_CSR = 3 ; /* HBA / 8, Interrupt ON, Enable */
|
||||
#if TICKDIV == 8
|
||||
SYSTICK_CSR = 3 ; /* HCLK / 8, Interrupt ON, Enable */
|
||||
#else
|
||||
SYSTICK_CSR = 7 ; /* HCLK, Interrupt ON, Enable */
|
||||
#endif
|
||||
/* SysTick_Handler will execute every 1s from now on */
|
||||
|
||||
#ifdef LED_ON
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* adcmain.c -- ADC reading of reference voltage and temperature sensor */
|
||||
/* Copyright (c) 2020-2021 Renaud Fivet */
|
||||
/* Copyright (c) 2020-2025 Renaud Fivet */
|
||||
|
||||
#include <stdio.h>
|
||||
#include "system.h"
|
||||
@ -7,7 +7,7 @@
|
||||
#define RAW
|
||||
|
||||
#define TS_CAL2 ((const short *) 0x1FFFF7C2)
|
||||
#define USER0 ((const unsigned char *) 0x1FFFF804)
|
||||
//#define USER0 ((const unsigned char *) 0x1FFFF804)
|
||||
|
||||
int main( void) {
|
||||
unsigned last = 0 ;
|
||||
@ -41,11 +41,11 @@ int main( void) {
|
||||
# else
|
||||
* 10000 / 5336 ;
|
||||
# endif
|
||||
Vsample = 330 * calV / Vsample ;
|
||||
Vsample = 3300 * calV / Vsample ;
|
||||
#else
|
||||
adc_vnt( VNT_VNC, &Vsample, &Csample) ;
|
||||
#endif
|
||||
printf( "%i.%i, %i.%i\n", Vsample / 100, Vsample % 100,
|
||||
printf( "%i.%i, %i.%i\n", Vsample / 1000, Vsample % 1000,
|
||||
Csample / 10, Csample % 10) ;
|
||||
}
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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>
|
||||
<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>
|
||||
|
||||
<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 <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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user