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