2424#include "periph/adc.h"
2525#include "periph/vbat.h"
2626
27+ #include "busy_wait.h"
28+
2729/**
2830 * @brief Default VBAT undefined value
2931 */
3032#ifndef VBAT_ADC
3133#define VBAT_ADC ADC_UNDEF
3234#endif
3335
36+ /* ADC register CR bits with HW property "rs":
37+ * Software can read as well as set this bit. We want to avoid writing a status
38+ * bit with a Read-Modify-Write cycle and accidentally setting other status
39+ * bits as well. Writing '0' has no effect on the bit value. */
40+ #define ADC_CR_BITS_PROPERTY_RS (ADC_CR_ADCAL | ADC_CR_ADSTP | ADC_CR_ADSTART \
41+ | ADC_CR_ADDIS | ADC_CR_ADEN)
42+
3443/**
3544 * @brief Allocate lock for the ADC device
3645 *
@@ -60,36 +69,127 @@ static inline void done(void)
6069 mutex_unlock (& lock );
6170}
6271
72+ static int _enable_adc (void )
73+ {
74+ /* check if the ADC is not already enabled */
75+ if (ADC1 -> CR & ADC_CR_ADEN ) {
76+ return 0 ;
77+ }
78+
79+ /* ensure the prerequisites are right */
80+ if (ADC1 -> CR & (ADC_CR_ADCAL | ADC_CR_ADSTP | ADC_CR_ADSTART | ADC_CR_ADDIS )) {
81+ return -1 ;
82+ }
83+
84+ /* enable the ADC and wait for the READY flag */
85+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADEN ;
86+
87+ while (!(ADC1 -> ISR & ADC_ISR_ADRDY )) {
88+ /* the calibration logic can reset the ADEN flag, so keep enabling it */
89+ if (!(ADC1 -> CR & ADC_CR_ADEN )) {
90+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADEN ;
91+ }
92+ }
93+
94+ return 0 ;
95+ }
96+
97+ static int _disable_adc (void )
98+ {
99+ /* check if disable is going on or ADC is disabled already */
100+ if ((ADC1 -> CR & ADC_CR_ADDIS ) || !(ADC1 -> CR & ADC_CR_ADEN )) {
101+ while (ADC1 -> CR & ADC_CR_ADDIS ) {}
102+ return 0 ;
103+ }
104+
105+ /* make sure no conversion is going on and stop it if it is */
106+ if (ADC1 -> CR & ADC_CR_ADSTART ) {
107+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADSTP ;
108+
109+ while (ADC1 -> CR & ADC_CR_ADSTP ) {}
110+ }
111+
112+ /* disable the ADC and wait until is is disabled*/
113+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADDIS ;
114+ while (!(ADC1 -> CR & ADC_CR_ADEN )) {}
115+
116+ return 0 ;
117+ }
118+
63119int adc_init (adc_t line )
64120{
65121 /* make sure the given line is valid */
66122 if (line >= ADC_NUMOF ) {
67123 return -1 ;
68124 }
69125
70- /* lock and power on the device */
126+ /* lock and power on the device, but keep it disabled */
71127 prep ();
128+ _disable_adc ();
129+
72130 /* configure the pin */
73131 if (adc_config [line ].pin != GPIO_UNDEF ) {
74132 gpio_init_analog (adc_config [line ].pin );
75133 }
76- /* reset configuration */
77- ADC1 -> CFGR2 = 0 ;
134+
135+ /* init ADC only if it wasn't already initialized. Check a register
136+ * set by the initialization which has a reset value of 0 */
137+ if (ADC1 -> SMPR == 0 ) {
138+
139+ /* reset configuration, including ADC_CFGR1_DMAEN | ADC_CFGR1_DMACFG | ADC_CFGR1_AUTOFF */
140+ ADC1 -> CFGR1 = 0 ;
141+ ADC1 -> CFGR2 = 0 ;
142+
143+ /* Calibration procedure according to:
144+ * - RM0360 section 12.3.2 for the STM32F0
145+ * - RM0454 section 14.3.3 for the STM32G0
146+ * - RM0490 section 16.4.3 for the STM32C0 */
147+
148+ /* only enable the ADC voltage regulator if the chip has one (STM32F0 does not) */
78149#if defined(ADC_CR_ADVREGEN )
79- /* calibrate ADC, per RM0454 section 14.3.3 */
80- /* 1. ensure ADEN=0, ADVREGEN=1, DMAEN=0 */
81- ADC1 -> CR |= ADC_CR_ADVREGEN ;
82- ADC1 -> CR &= ~(ADC_CR_ADCAL | ADC_CR_ADEN );
83- ADC1 -> CFGR1 &= ~(ADC_CFGR1_DMAEN );
84- /* 2. Set ADCAL=1 */
85- ADC1 -> CR |= ADC_CR_ADCAL ;
86- /* 3. Wait for ADCAL=0 (or EOCAL=1) */
87- while ((ADC1 -> ISR & ADC_ISR_EOCAL )) {}
150+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADVREGEN ;
151+
152+ /* wait for t_ADCVREG_STUP = 20us with some headroom due to busy_wait_us being inaccurate */
153+ busy_wait_us (100 );
154+ #endif
155+
156+ /* the STM32C0 requires an averaging of eight calibration values */
157+ #if defined(CPU_FAM_STM32C0 ) || defined(CPU_FAM_STM32G0 )
158+ uint32_t calfact = 0 ;
159+
160+ for (uint32_t i = 8 ; i > 0 ; i -- ) {
161+ /* perform a calibration and wait for the flag to clear */
162+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADCAL ;
163+ while (ADC1 -> CR & ADC_CR_ADCAL ) {}
164+
165+ calfact += ADC1 -> CALFACT ;
166+ }
167+ /* round up to the nearest integer */
168+ calfact = (calfact + 4 ) / 8 ;
169+
170+ /* enable the ADC to write the calibration factor and wait before writing and disabling */
171+ if (_enable_adc () == -1 ) {
172+ return -1 ;
173+ }
174+ busy_wait_us (100 );
175+
176+ /* apply the calibration factor and mask it in case it is bigger than 0x7F */
177+ ADC1 -> CALFACT = calfact & ADC_CALFACT_CALFACT ;
178+
179+ _disable_adc ();
180+
181+ /* configure sampling time to a safe value */
182+ ADC1 -> SMPR = ADC_SMPR_SMP1_2 | ADC_SMPR_SMP1_0 ; /* 39.5 ADC clock cycles */
183+ #else
184+ /* perform a calibration and wait for the flag to clear */
185+ ADC1 -> CR = (ADC1 -> CR & ~ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADCAL ;
186+ while (ADC1 -> CR & ADC_CR_ADCAL ) {}
187+
188+ /* configure sampling time to safe value */
189+ ADC1 -> SMPR = ADC_SMPR_SMP_1 | ADC_SMPR1_SMPR_0 ; /* 28.5 ADC clock cycles */
88190#endif
89- /* enable device */
90- ADC1 -> CR = ADC_CR_ADEN ;
91- /* configure sampling time to save value */
92- ADC1 -> SMPR = 0x3 ; /* 28.5 ADC clock cycles */
191+ }
192+
93193 /* power off an release device for now */
94194 done ();
95195
@@ -107,24 +207,41 @@ int32_t adc_sample(adc_t line, adc_res_t res)
107207
108208 /* lock and power on the ADC device */
109209 prep ();
210+
110211 /* check if this is the VBAT line */
111212 if (IS_USED (MODULE_PERIPH_VBAT ) && line == VBAT_ADC ) {
112213 vbat_enable ();
113214 }
215+
114216 /* set resolution and channel */
115217 ADC1 -> CFGR1 = res ;
116218 ADC1 -> CHSELR = (1 << adc_config [line ].chan );
219+
220+ /* check if the ADC was enabled successfully */
221+ if (_enable_adc () == -1 ) {
222+ done ();
223+ return -1 ;
224+ }
225+
117226 /* start conversion and wait for results */
118- ADC1 -> CR |= ADC_CR_ADSTART ;
227+ ADC1 -> CR = ( ADC1 -> CR & ~ ADC_CR_BITS_PROPERTY_RS ) | ADC_CR_ADSTART ;
119228 while (!(ADC1 -> ISR & ADC_ISR_EOC )) {}
229+
120230 /* read result */
121231 sample = (int )ADC1 -> DR ;
232+
122233 /* check if this is the VBAT line */
123234 if (IS_USED (MODULE_PERIPH_VBAT ) && line == VBAT_ADC ) {
124235 vbat_disable ();
125236 }
126- /* unlock and power off device again */
237+
238+ /* disable, unlock and power off device again */
239+ int ret = _disable_adc ();
127240 done ();
128241
129- return sample ;
242+ if (ret == -1 ) {
243+ return -1 ;
244+ } else {
245+ return sample ;
246+ }
130247}
0 commit comments