1 /***************************************************************************//**
2 * @file
3 * @brief Analog to Digital Converter (ADC) Peripheral API
4 * @author Energy Micro AS
5 * @version 3.0.0
6 *******************************************************************************
7 * @section License
8 * <b>(C) Copyright 2012 Energy Micro AS, http://www.energymicro.com</b>
9 *******************************************************************************
10 *
11 * Permission is granted to anyone to use this software for any purpose,
12 * including commercial applications, and to alter it and redistribute it
13 * freely, subject to the following restrictions:
14 *
15 * 1. The origin of this software must not be misrepresented; you must not
16 * claim that you wrote the original software.
17 * 2. Altered source versions must be plainly marked as such, and must not be
18 * misrepresented as being the original software.
19 * 3. This notice may not be removed or altered from any source distribution.
20 *
21 * DISCLAIMER OF WARRANTY/LIMITATION OF REMEDIES: Energy Micro AS has no
22 * obligation to support this Software. Energy Micro AS is providing the
23 * Software "AS IS", with no express or implied warranties of any kind,
24 * including, but not limited to, any implied warranties of merchantability
25 * or fitness for any particular purpose or warranties against infringement
26 * of any proprietary rights of a third party.
27 *
28 * Energy Micro AS will not be liable for any consequential, incidental, or
29 * special damages, or any other relief, or for any claim by any third party,
30 * arising from your use of this Software.
31 *
32 ******************************************************************************/
33 #include "em_adc.h"
34 #include "em_cmu.h"
35 #include "em_assert.h"
36
37 /***************************************************************************//**
38 * @addtogroup EM_Library
39 * @{
40 ******************************************************************************/
41
42 /***************************************************************************//**
43 * @addtogroup ADC
44 * @brief Analog to Digital Converter (ADC) Peripheral API
45 * @{
46 ******************************************************************************/
47
48 /*******************************************************************************
49 ******************************* DEFINES ***********************************
50 ******************************************************************************/
51
52 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
53
54 /** Validation of ADC register block pointer reference for assert statements. */
55 #define ADC_REF_VALID(ref) ((ref) == ADC0)
56
57 /** Max ADC clock */
58 #define ADC_MAX_CLOCK 13000000
59
60 /** Min ADC clock */
61 #define ADC_MIN_CLOCK 32000
62
63 /** @endcond */
64
65
66 /*******************************************************************************
67 *************************** LOCAL FUNCTIONS *******************************
68 ******************************************************************************/
69
70 /** @cond DO_NOT_INCLUDE_WITH_DOXYGEN */
71
72 /***************************************************************************//**
73 * @brief
74 * Load SCAN calibrate register with predefined values for a certain
75 * reference.
76 *
77 * @details
78 * During production, calibration values are made and stored in the device
79 * information page for known references. Notice that for external references,
80 * calibration values must be determined explicitly, and this function
81 * will not modify the calibration register.
82 *
83 * @param[in] adc
84 * Pointer to ADC peripheral register block.
85 *
86 * @param[in] ref
87 * Reference to load calibrated values for. No values are loaded for
88 * external references.
89 ******************************************************************************/
ADC_CalibrateLoadScan(ADC_TypeDef * adc,ADC_Ref_TypeDef ref)90 static void ADC_CalibrateLoadScan(ADC_TypeDef *adc, ADC_Ref_TypeDef ref)
91 {
92 uint32_t cal;
93
94 /* Load proper calibration data depending on selected reference */
95 /* NOTE: We use ...SCAN... defines below, they are the same as */
96 /* similar ...SINGLE... defines. */
97 switch (ref)
98 {
99 case adcRef1V25:
100 cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK);
101 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >>
102 _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT;
103 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >>
104 _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT;
105 adc->CAL = cal;
106 break;
107
108 case adcRef2V5:
109 cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK);
110 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_GAIN_MASK) >>
111 _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT;
112 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK) >>
113 _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT;
114 adc->CAL = cal;
115 break;
116
117 case adcRefVDD:
118 cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK);
119 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_GAIN_MASK) >>
120 _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT;
121 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK) >>
122 _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT;
123 adc->CAL = cal;
124 break;
125
126 case adcRef5VDIFF:
127 cal = adc->CAL & ~(_ADC_CAL_SCANOFFSET_MASK | _ADC_CAL_SCANGAIN_MASK);
128 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK) >>
129 _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT) << _ADC_CAL_SCANGAIN_SHIFT;
130 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK) >>
131 _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT;
132 adc->CAL = cal;
133 break;
134
135 case adcRef2xVDD:
136 /* Gain value not of relevance for this reference, leave as is */
137 cal = adc->CAL & ~_ADC_CAL_SCANOFFSET_MASK;
138 cal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK) >>
139 _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT) << _ADC_CAL_SCANOFFSET_SHIFT;
140 adc->CAL = cal;
141 break;
142
143 /* For external references, the calibration must be determined for the */
144 /* specific application and set explicitly. */
145 default:
146 break;
147 }
148 }
149
150 /***************************************************************************//**
151 * @brief
152 * Load SINGLE calibrate register with predefined values for a certain
153 * reference.
154 *
155 * @details
156 * During production, calibration values are made and stored in the device
157 * information page for known references. Notice that for external references,
158 * calibration values must be determined explicitly, and this function
159 * will not modify the calibration register.
160 *
161 * @param[in] adc
162 * Pointer to ADC peripheral register block.
163 *
164 * @param[in] ref
165 * Reference to load calibrated values for. No values are loaded for
166 * external references.
167 ******************************************************************************/
ADC_CalibrateLoadSingle(ADC_TypeDef * adc,ADC_Ref_TypeDef ref)168 static void ADC_CalibrateLoadSingle(ADC_TypeDef *adc, ADC_Ref_TypeDef ref)
169 {
170 uint32_t cal;
171
172 /* Load proper calibration data depending on selected reference */
173 /* NOTE: We use ...SCAN... defines below, they are the same as */
174 /* similar ...SINGLE... defines. */
175 switch (ref)
176 {
177 case adcRef1V25:
178 cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK);
179 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >>
180 _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT;
181 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >>
182 _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT;
183 adc->CAL = cal;
184 break;
185
186 case adcRef2V5:
187 cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK);
188 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_GAIN_MASK) >>
189 _DEVINFO_ADC0CAL0_2V5_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT;
190 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_2V5_OFFSET_MASK) >>
191 _DEVINFO_ADC0CAL0_2V5_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT;
192 adc->CAL = cal;
193 break;
194
195 case adcRefVDD:
196 cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK);
197 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_GAIN_MASK) >>
198 _DEVINFO_ADC0CAL1_VDD_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT;
199 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_VDD_OFFSET_MASK) >>
200 _DEVINFO_ADC0CAL1_VDD_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT;
201 adc->CAL = cal;
202 break;
203
204 case adcRef5VDIFF:
205 cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK);
206 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_GAIN_MASK) >>
207 _DEVINFO_ADC0CAL1_5VDIFF_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT;
208 cal |= ((DEVINFO->ADC0CAL1 & _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_MASK) >>
209 _DEVINFO_ADC0CAL1_5VDIFF_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT;
210 adc->CAL = cal;
211 break;
212
213 case adcRef2xVDD:
214 /* Gain value not of relevance for this reference, leave as is */
215 cal = adc->CAL & ~_ADC_CAL_SINGLEOFFSET_MASK;
216 cal |= ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_MASK) >>
217 _DEVINFO_ADC0CAL2_2XVDDVSS_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT;
218 adc->CAL = cal;
219 break;
220
221 /* For external references, the calibration must be determined for the */
222 /* specific application and set explicitly. */
223 default:
224 break;
225 }
226 }
227
228 /** @endcond */
229
230 /*******************************************************************************
231 ************************** GLOBAL FUNCTIONS *******************************
232 ******************************************************************************/
233
234 /***************************************************************************//**
235 * @brief
236 * Initialize ADC.
237 *
238 * @details
239 * Initializes common parts for both single conversion and scan sequence.
240 * In addition, single and/or scan control configuration must be done, please
241 * refer to ADC_InitSingle() and ADC_InitScan() respectively.
242 *
243 * @note
244 * This function will stop any ongoing conversion.
245 *
246 * @param[in] adc
247 * Pointer to ADC peripheral register block.
248 *
249 * @param[in] init
250 * Pointer to ADC initialization structure.
251 ******************************************************************************/
ADC_Init(ADC_TypeDef * adc,const ADC_Init_TypeDef * init)252 void ADC_Init(ADC_TypeDef *adc, const ADC_Init_TypeDef *init)
253 {
254 uint32_t tmp;
255
256 EFM_ASSERT(ADC_REF_VALID(adc));
257
258 /* Make sure conversion is not in progress */
259 adc->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP;
260
261 tmp = ((uint32_t)(init->ovsRateSel) << _ADC_CTRL_OVSRSEL_SHIFT) |
262 (((uint32_t)(init->timebase) << _ADC_CTRL_TIMEBASE_SHIFT) & _ADC_CTRL_TIMEBASE_MASK) |
263 (((uint32_t)(init->prescale) << _ADC_CTRL_PRESC_SHIFT) & _ADC_CTRL_PRESC_MASK) |
264 ((uint32_t)(init->lpfMode) << _ADC_CTRL_LPFMODE_SHIFT) |
265 ((uint32_t)(init->warmUpMode) << _ADC_CTRL_WARMUPMODE_SHIFT);
266
267 if (init->tailgate)
268 {
269 tmp |= ADC_CTRL_TAILGATE;
270 }
271
272 adc->CTRL = tmp;
273 }
274
275
276 /***************************************************************************//**
277 * @brief
278 * Initialize ADC scan sequence.
279 *
280 * @details
281 * Please refer to ADC_StartScan() for starting scan sequence.
282 *
283 * When selecting an external reference, the gain and offset calibration
284 * must be set explicitly (CAL register). For other references, the
285 * calibration is updated with values defined during manufacturing.
286 *
287 * @note
288 * This function will stop any ongoing scan sequence.
289 *
290 * @param[in] adc
291 * Pointer to ADC peripheral register block.
292 *
293 * @param[in] init
294 * Pointer to ADC initialization structure.
295 ******************************************************************************/
ADC_InitScan(ADC_TypeDef * adc,const ADC_InitScan_TypeDef * init)296 void ADC_InitScan(ADC_TypeDef *adc, const ADC_InitScan_TypeDef *init)
297 {
298 uint32_t tmp;
299
300 EFM_ASSERT(ADC_REF_VALID(adc));
301
302 /* Make sure scan sequence is not in progress */
303 adc->CMD = ADC_CMD_SCANSTOP;
304
305 /* Load proper calibration data depending on selected reference */
306 ADC_CalibrateLoadScan(adc, init->reference);
307
308 tmp = ((uint32_t)(init->prsSel) << _ADC_SCANCTRL_PRSSEL_SHIFT) |
309 ((uint32_t)(init->acqTime) << _ADC_SCANCTRL_AT_SHIFT) |
310 ((uint32_t)(init->reference) << _ADC_SCANCTRL_REF_SHIFT) |
311 init->input |
312 ((uint32_t)(init->resolution) << _ADC_SCANCTRL_RES_SHIFT);
313
314 if (init->prsEnable)
315 {
316 tmp |= ADC_SCANCTRL_PRSEN;
317 }
318
319 if (init->leftAdjust)
320 {
321 tmp |= ADC_SCANCTRL_ADJ_LEFT;
322 }
323
324 if (init->diff)
325 {
326 tmp |= ADC_SCANCTRL_DIFF;
327 }
328
329 if (init->rep)
330 {
331 tmp |= ADC_SCANCTRL_REP;
332 }
333
334 adc->SCANCTRL = tmp;
335 }
336
337
338 /***************************************************************************//**
339 * @brief
340 * Initialize single ADC sample conversion.
341 *
342 * @details
343 * Please refer to ADC_StartSingle() for starting single conversion.
344 *
345 * When selecting an external reference, the gain and offset calibration
346 * must be set explicitly (CAL register). For other references, the
347 * calibration is updated with values defined during manufacturing.
348 *
349 * @note
350 * This function will stop any ongoing single conversion.
351 *
352 * @param[in] adc
353 * Pointer to ADC peripheral register block.
354 *
355 * @param[in] init
356 * Pointer to ADC initialization structure.
357 ******************************************************************************/
ADC_InitSingle(ADC_TypeDef * adc,const ADC_InitSingle_TypeDef * init)358 void ADC_InitSingle(ADC_TypeDef *adc, const ADC_InitSingle_TypeDef *init)
359 {
360 uint32_t tmp;
361
362 EFM_ASSERT(ADC_REF_VALID(adc));
363
364 /* Make sure single conversion is not in progress */
365 adc->CMD = ADC_CMD_SINGLESTOP;
366
367 /* Load proper calibration data depending on selected reference */
368 ADC_CalibrateLoadSingle(adc, init->reference);
369
370 tmp = ((uint32_t)(init->prsSel) << _ADC_SINGLECTRL_PRSSEL_SHIFT) |
371 ((uint32_t)(init->acqTime) << _ADC_SINGLECTRL_AT_SHIFT) |
372 ((uint32_t)(init->reference) << _ADC_SINGLECTRL_REF_SHIFT) |
373 ((uint32_t)(init->input) << _ADC_SINGLECTRL_INPUTSEL_SHIFT) |
374 ((uint32_t)(init->resolution) << _ADC_SINGLECTRL_RES_SHIFT);
375
376 if (init->prsEnable)
377 {
378 tmp |= ADC_SINGLECTRL_PRSEN;
379 }
380
381 if (init->leftAdjust)
382 {
383 tmp |= ADC_SINGLECTRL_ADJ_LEFT;
384 }
385
386 if (init->diff)
387 {
388 tmp |= ADC_SINGLECTRL_DIFF;
389 }
390
391 if (init->rep)
392 {
393 tmp |= ADC_SINGLECTRL_REP;
394 }
395
396 adc->SINGLECTRL = tmp;
397 }
398
399
400 /***************************************************************************//**
401 * @brief
402 * Calculate prescaler value used to determine ADC clock.
403 *
404 * @details
405 * The ADC clock is given by: HFPERCLK / (prescale + 1).
406 *
407 * @param[in] adcFreq ADC frequency wanted. The frequency will automatically
408 * be adjusted to be within valid range according to reference manual.
409 *
410 * @param[in] hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to
411 * use currently defined HFPER clock setting.
412 *
413 * @return
414 * Prescaler value to use for ADC in order to achieve a clock value
415 * <= @p adcFreq.
416 ******************************************************************************/
ADC_PrescaleCalc(uint32_t adcFreq,uint32_t hfperFreq)417 uint8_t ADC_PrescaleCalc(uint32_t adcFreq, uint32_t hfperFreq)
418 {
419 uint32_t ret;
420
421 /* Make sure selected ADC clock is within valid range */
422 if (adcFreq > ADC_MAX_CLOCK)
423 {
424 adcFreq = ADC_MAX_CLOCK;
425 }
426 else if (adcFreq < ADC_MIN_CLOCK)
427 {
428 adcFreq = ADC_MIN_CLOCK;
429 }
430
431 /* Use current HFPER frequency? */
432 if (!hfperFreq)
433 {
434 hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER);
435 }
436
437 ret = (hfperFreq + adcFreq - 1) / adcFreq;
438 if (ret)
439 {
440 ret--;
441 }
442
443 return (uint8_t)ret;
444 }
445
446
447 /***************************************************************************//**
448 * @brief
449 * Reset ADC to same state as after a HW reset.
450 *
451 * @note
452 * The ROUTE register is NOT reset by this function, in order to allow for
453 * centralized setup of this feature.
454 *
455 * @param[in] adc
456 * Pointer to ADC peripheral register block.
457 ******************************************************************************/
ADC_Reset(ADC_TypeDef * adc)458 void ADC_Reset(ADC_TypeDef *adc)
459 {
460 uint32_t cal;
461
462 /* Stop conversions, before resetting other registers. */
463 adc->CMD = ADC_CMD_SINGLESTOP | ADC_CMD_SCANSTOP;
464 adc->SINGLECTRL = _ADC_SINGLECTRL_RESETVALUE;
465 adc->SCANCTRL = _ADC_SCANCTRL_RESETVALUE;
466 adc->CTRL = _ADC_CTRL_RESETVALUE;
467 adc->IEN = _ADC_IEN_RESETVALUE;
468 adc->IFC = _ADC_IFC_MASK;
469 adc->BIASPROG = _ADC_BIASPROG_RESETVALUE;
470
471 cal = adc->CAL & ~(_ADC_CAL_SINGLEOFFSET_MASK | _ADC_CAL_SINGLEGAIN_MASK);
472 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_GAIN_MASK) >>
473 _DEVINFO_ADC0CAL0_1V25_GAIN_SHIFT) << _ADC_CAL_SINGLEGAIN_SHIFT;
474 cal |= ((DEVINFO->ADC0CAL0 & _DEVINFO_ADC0CAL0_1V25_OFFSET_MASK) >>
475 _DEVINFO_ADC0CAL0_1V25_OFFSET_SHIFT) << _ADC_CAL_SINGLEOFFSET_SHIFT;
476 adc->CAL = cal;
477
478 /* Do not reset route register, setting should be done independently */
479 }
480
481
482 /***************************************************************************//**
483 * @brief
484 * Calculate timebase value in order to get a timebase providing at least 1us.
485 *
486 * @param[in] hfperFreq Frequency in Hz of reference HFPER clock. Set to 0 to
487 * use currently defined HFPER clock setting.
488 *
489 * @return
490 * Timebase value to use for ADC in order to achieve at least 1 us.
491 ******************************************************************************/
ADC_TimebaseCalc(uint32_t hfperFreq)492 uint8_t ADC_TimebaseCalc(uint32_t hfperFreq)
493 {
494 if (!hfperFreq)
495 {
496 hfperFreq = CMU_ClockFreqGet(cmuClock_HFPER);
497
498 /* Just in case, make sure we get non-zero freq for below calculation */
499 if (!hfperFreq)
500 {
501 hfperFreq = 1;
502 }
503 }
504 #if defined(_EFM32_GIANT_FAMILY)
505 /* Handle errata on Giant Gecko, max TIMEBASE is 5 bits wide or max 0x1F */
506 /* cycles. This will give a warmp up time of e.g. 0.645us, not the */
507 /* required 1us when operating at 48MHz. One must also increase acqTime */
508 /* to compensate for the missing clock cycles, adding up to 1us in total.*/
509 /* See reference manual for details. */
510 if( hfperFreq > 32000000 )
511 {
512 hfperFreq = 32000000;
513 }
514 #endif
515 /* Determine number of HFPERCLK cycle >= 1us */
516 hfperFreq += 999999;
517 hfperFreq /= 1000000;
518
519 /* Return timebase value (N+1 format) */
520 return (uint8_t)(hfperFreq - 1);
521 }
522
523
524 /** @} (end addtogroup ADC) */
525 /** @} (end addtogroup EM_Library) */
526