-
Notifications
You must be signed in to change notification settings - Fork 2k
/
adc_wl.c
159 lines (128 loc) · 3.82 KB
/
adc_wl.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
* Copyright (C) 2014-2016 Freie Universität Berlin
* Copyright (C) 2018 HAW-Hamburg
* Copyright (C) 2021 Inria
*
* This file is subject to the terms and conditions of the GNU Lesser General
* Public License v2.1. See the file LICENSE in the top level directory for more
* details.
*/
/**
* @ingroup cpu_stm32
* @ingroup drivers_periph_adc
* @{
*
* @file
* @brief Low-level ADC driver implementation
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
* @author Michel Rottleuthner <michel.rottleuthner@haw-hamburg.de>
* @author Francisco Molina <francois-xavier.molina@inria.fr>
*
* @}
*/
#include "cpu.h"
#include "mutex.h"
#include "periph/adc.h"
#include "periph_conf.h"
#include "periph/vbat.h"
#include "ztimer.h"
/**
* @brief Default VBAT undefined value
*/
#ifndef VBAT_ADC
#define VBAT_ADC ADC_UNDEF
#endif
/**
* @brief Allocate lock for the ADC device
*
* All STM32WL CPUs we support so far only come with a single ADC device.
*/
static mutex_t lock = MUTEX_INIT;
static inline void prep(void)
{
mutex_lock(&lock);
periph_clk_en(APB2, RCC_APB2ENR_ADCEN);
}
static inline void done(void)
{
periph_clk_dis(APB2, RCC_APB2ENR_ADCEN);
mutex_unlock(&lock);
}
int adc_init(adc_t line)
{
/* check if the line is valid */
if (line >= ADC_NUMOF) {
return -1;
}
/* lock device and enable its peripheral clock */
prep();
/* configure the pin */
if (adc_config[line].pin != GPIO_UNDEF) {
gpio_init_analog(adc_config[line].pin);
}
/* init ADC line only if it wasn't already initialized */
if (!(ADC->CR & (ADC_CR_ADEN))) {
/* set prescaler to 0 to let the ADC run with maximum speed */
ADC_COMMON->CCR &= ~(ADC_CCR_PRESC);
/* set ADC clock to PCLK/2 otherwise */
ADC->CFGR2 &= ~(ADC_CFGR2_CKMODE_0 | ADC_CFGR2_CKMODE_1);
ADC->CFGR2 |= ADC_CFGR2_CKMODE_0;
/* enable ADC internal voltage regulator and wait for startup period */
ADC->CR |= (ADC_CR_ADVREGEN);
#if IS_USED(MODULE_ZTIMER_USEC)
ztimer_sleep(ZTIMER_USEC, ADC_T_ADCVREG_STUP_US);
#else
/* to avoid using ZTIMER_USEC unless already included round up the
internal voltage regulator start up to 1ms */
ztimer_sleep(ZTIMER_MSEC, 1);
#endif
/* ´start automatic calibration and wait for it to complete */
ADC->CR |= ADC_CR_ADCAL;
while (ADC->CR & ADC_CR_ADCAL) {}
/* clear ADRDY by writing it*/
ADC->ISR |= (ADC_ISR_ADRDY);
/* enable ADC and wait for it to be ready */
ADC->CR |= (ADC_CR_ADEN);
while ((ADC->ISR & ADC_ISR_ADRDY) == 0) {}
/* set sequence length to 1 conversion */
ADC->CFGR1 &= ~ADC_CFGR1_CONT;
/* Sampling time of 3.5 ADC clocks for all channels*/
ADC->SMPR = 0x0101;
}
/* free the device again */
done();
return 0;
}
int32_t adc_sample(adc_t line, adc_res_t res)
{
int sample;
/* check if resolution is applicable */
if (res & 0x3) {
return -1;
}
/* lock and power on the ADC device */
prep();
/* check if this is the VBAT line */
if (IS_USED(MODULE_PERIPH_VBAT) && line == VBAT_ADC) {
vbat_enable();
}
/* first clear resolution */
ADC->CFGR1 &= ~ADC_CFGR1_RES;
/* then set resolution to the required value*/
ADC->CFGR1 |= res;
/* specify channel for regular conversion */
ADC->CHSELR = (1 << adc_config[line].chan);
/* start conversion and wait for it to complete */
ADC->CR |= ADC_CR_ADSTART;
while (!(ADC->ISR & ADC_ISR_EOC)) {}
/* read the sample */
sample = (int)ADC->DR;
/* check if this is the VBAT line */
if (IS_USED(MODULE_PERIPH_VBAT) && line == VBAT_ADC) {
vbat_disable();
}
/* free the device again */
done();
return sample;
}