adc.c
5.63 KB
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/*
* Copyright (C) 2017 Dan Evans <photonthunder@gmail.com>
* Copyright (C) 2017 Travis Griggs <travisgriggs@gmail.com>
* Copyright (C) 2017 Dylan Laduranty <dylanladuranty@gmail.com>
*
* 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_sam0_common
* @ingroup drivers_periph_adc
* @{
*
* @file
* @brief Low-level ADC driver implementation
*
* @}
*/
#include <stdint.h>
#include "cpu.h"
#include "periph/gpio.h"
#include "periph/adc.h"
#include "periph_conf.h"
#include "mutex.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/* Only if we actually have any ADCs */
#if ADC_NUMOF
/* ADC 0 device configuration */
#define ADC_0_DEV ADC
#define ADC_0_IRQ ADC_IRQn
/* Prototypes */
static bool _adc_syncing(void);
static void _adc_poweroff(void);
static int _adc_configure(adc_res_t res);
static mutex_t _lock = MUTEX_INIT;
static inline void _prep(void)
{
mutex_lock(&_lock);
}
static inline void _done(void)
{
mutex_unlock(&_lock);
}
static bool _adc_syncing(void)
{
#ifdef CPU_SAMD21
if (ADC_0_DEV->STATUS.reg & ADC_STATUS_SYNCBUSY) {
return true;
}
#else /* CPU_SAML21 */
if (ADC_0_DEV->SYNCBUSY.reg) {
return true;
}
#endif
return false;
}
static void _adc_poweroff(void)
{
while (_adc_syncing()) {}
/* Disable */
ADC_0_DEV->CTRLA.reg &= ~ADC_CTRLA_ENABLE;
while (_adc_syncing()) {}
/* Disable bandgap */
#ifdef CPU_SAMD21
if (ADC_0_REF_DEFAULT == ADC_REFCTRL_REFSEL_INT1V) {
SYSCTRL->VREF.reg &= ~SYSCTRL_VREF_BGOUTEN;
}
#else /* CPU_SAML21 */
if (ADC_0_REF_DEFAULT == ADC_REFCTRL_REFSEL_INTREF) {
SUPC->VREF.reg &= ~SUPC_VREF_VREFOE;
}
#endif
}
static int _adc_configure(adc_res_t res)
{
/* Individual comparison necessary because ADC Resolution Bits are not
* numerically in order and 16Bit (averaging - not currently supported)
* falls between 12bit and 10bit. See datasheet for details */
assert((res == ADC_RES_8BIT) || (res == ADC_RES_10BIT) ||
(res == ADC_RES_12BIT));
_adc_poweroff();
if (ADC_0_DEV->CTRLA.reg & ADC_CTRLA_SWRST ||
ADC_0_DEV->CTRLA.reg & ADC_CTRLA_ENABLE ) {
_done();
DEBUG("adc: not ready\n");
return -1;
}
#ifdef CPU_SAMD21
/* Power On */
PM->APBCMASK.reg |= PM_APBCMASK_ADC;
/* GCLK Setup */
GCLK->CLKCTRL.reg = (uint32_t)(GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
(GCLK_CLKCTRL_ID(ADC_GCLK_ID)));
/* Configure CTRLB Register HERE IS THE RESOLUTION SET! */
ADC_0_DEV->CTRLB.reg = ADC_0_PRESCALER | res;
/* Load the fixed device calibration constants */
ADC_0_DEV->CALIB.reg =
ADC_CALIB_BIAS_CAL((*(uint32_t*)ADC_FUSES_BIASCAL_ADDR >>
ADC_FUSES_BIASCAL_Pos)) |
ADC_CALIB_LINEARITY_CAL((*(uint64_t*)ADC_FUSES_LINEARITY_0_ADDR >>
ADC_FUSES_LINEARITY_0_Pos));
/* Set Voltage Reference */
ADC_0_DEV->REFCTRL.reg = ADC_0_REF_DEFAULT;
/* Disable all interrupts */
ADC_0_DEV->INTENCLR.reg = (ADC_INTENCLR_SYNCRDY) | (ADC_INTENCLR_WINMON) |
(ADC_INTENCLR_OVERRUN) | (ADC_INTENCLR_RESRDY);
while (_adc_syncing()) {}
/* Enable bandgap if VREF is internal 1V */
if (ADC_0_REF_DEFAULT == ADC_REFCTRL_REFSEL_INT1V) {
SYSCTRL->VREF.reg |= SYSCTRL_VREF_BGOUTEN;
}
#else /* CPU_SAML21 */
/* Power on */
MCLK->APBDMASK.reg |= MCLK_APBDMASK_ADC;
/* GCLK Setup */
GCLK->PCHCTRL[ADC_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK0;
/* Set Voltage Reference */
ADC_0_DEV->REFCTRL.reg = ADC_0_REF_DEFAULT;
/* Configure CTRLB & CTRLC Register */
ADC_0_DEV->CTRLB.reg = ADC_0_PRESCALER;
ADC_0_DEV->CTRLC.reg |= res;
/* Disable all interrupts */
ADC_0_DEV->INTENCLR.reg = ADC_INTENCLR_WINMON | ADC_INTENCLR_OVERRUN |
ADC_INTENCLR_RESRDY;
/* Set default calibration from NVM */
ADC_0_DEV->CALIB.reg =
ADC_FUSES_BIASCOMP((*(uint32_t*)ADC_FUSES_BIASCOMP_ADDR)) >>
ADC_CALIB_BIASCOMP_Pos |
ADC_FUSES_BIASREFBUF((*(uint32_t*)ADC_FUSES_BIASREFBUF_ADDR) >>
ADC_FUSES_BIASREFBUF_Pos);
while (_adc_syncing()) {}
/* Enable bandgap if necessary */
if (ADC_0_REF_DEFAULT == ADC_REFCTRL_REFSEL_INTREF) {
SUPC->VREF.reg |= SUPC_VREF_VREFOE;
}
#endif
/* Enable ADC Module */
ADC_0_DEV->CTRLA.reg |= ADC_CTRLA_ENABLE;
while (_adc_syncing()) {}
return 0;
}
int adc_init(adc_t line)
{
_prep();
gpio_init(adc_channels[line].pin, GPIO_IN);
gpio_init_mux(adc_channels[line].pin, GPIO_MUX_B);
_done();
return 0;
}
int adc_sample(adc_t line, adc_res_t res)
{
if (line >= ADC_NUMOF) {
DEBUG("adc: line arg not applicable\n");
return -1;
}
_prep();
if (_adc_configure(res) != 0) {
_done();
DEBUG("adc: configuration failed\n");
return -1;
}
#ifdef CPU_SAMD21
ADC_0_DEV->INPUTCTRL.reg = ADC_0_GAIN_FACTOR_DEFAULT |
adc_channels[line].muxpos | ADC_0_NEG_INPUT;
#else /* CPU_SAML21 */
ADC_0_DEV->INPUTCTRL.reg = adc_channels[line].muxpos | ADC_0_NEG_INPUT;
#endif
while (_adc_syncing()) {}
/* Start the conversion */
ADC_0_DEV->SWTRIG.reg = ADC_SWTRIG_START;
/* Wait for the result */
while (!(ADC_0_DEV->INTFLAG.reg & ADC_INTFLAG_RESRDY)) {}
int result = ADC_0_DEV->RESULT.reg;
_adc_poweroff();
_done();
return result;
}
#endif /* #if ADC_NUMOF */