Embedded System Design 2 - Project
adc.c
Go to the documentation of this file.
1 /***************************************************************************//**
2  * @file adc.c
3  * @brief ADC functionality for reading the (battery) voltage and internal temperature.
4  * @version 2.1
5  * @author Brecht Van Eeckhoudt
6  *
7  * ******************************************************************************
8  *
9  * @section Versions
10  *
11  * @li v1.0: Moved ADC functionality from `other.c` to this file.
12  * @li v1.1: Removed re-initialization dbprint messages.
13  * @li v1.2: Started using custom enum type and cleaned up some unnecessary statements after testing.
14  * @li v1.3: Changed error numbering.
15  * @li v1.4: Added timeout to `while` loop and changed types to `int32_t`.
16  * @li v1.5: Refined timout functionality.
17  * @li v2.0: Disabled peripheral clock before entering an `error` function, added
18  * functionality to exit methods after `error` call and updated version number.
19  * @li v2.1: Removed `static` before the local variables (not necessary).
20  *
21  * ******************************************************************************
22  *
23  * @section License
24  *
25  * **Copyright (C) 2019 - Brecht Van Eeckhoudt**
26  *
27  * This program is free software: you can redistribute it and/or modify
28  * it under the terms of the **GNU General Public License** as published by
29  * the Free Software Foundation, either **version 3** of the License, or
30  * (at your option) any later version.
31  *
32  * This program is distributed in the hope that it will be useful,
33  * but WITHOUT ANY WARRANTY; without even the implied warranty of
34  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35  * GNU General Public License for more details.
36  *
37  * *A copy of the GNU General Public License can be found in the `LICENSE`
38  * file along with this source code.*
39  *
40  * @n
41  *
42  * Some methods use code obtained from examples from [Silicon Labs' GitHub](https://github.com/SiliconLabs/peripheral_examples).
43  * These sections are licensed under the Silabs License Agreement. See the file
44  * "Silabs_License_Agreement.txt" for details. Before using this software for
45  * any purpose, you must agree to the terms of that agreement.
46  *
47  ******************************************************************************/
48 
49 
50 #include <stdint.h> /* (u)intXX_t */
51 #include <stdbool.h> /* "bool", "true", "false" */
52 #include "em_device.h" /* Include necessary MCU-specific header file */
53 #include "em_cmu.h" /* Clock management unit */
54 #include "em_adc.h" /* Analog to Digital Converter */
55 
56 #include "adc.h" /* Corresponding header file */
57 #include "debug_dbprint.h" /* Enable or disable printing to UART for debugging */
58 #include "util.h" /* Utility functionality */
59 
60 
61 /* Local definitions */
62 /** Enable (1) or disable (0) printing the timeout counter value using DBPRINT */
63 #define DBPRINT_TIMEOUT 0
64 
65 /** Maximum value for the counter before exiting a `while` loop */
66 #define TIMEOUT_CONVERSION 50
67 
68 
69 /* Local variables */
70 volatile bool adcConversionComplete = false; /* Volatile because it's modified by an interrupt service routine */
71 ADC_Init_TypeDef init = ADC_INIT_DEFAULT;
72 ADC_InitSingle_TypeDef initSingle = ADC_INITSINGLE_DEFAULT;
73 
74 
75 /* Local prototype */
76 static float32_t convertToCelsius (int32_t adcSample);
77 
78 
79 /**************************************************************************//**
80  * @brief
81  * Method to initialize the ADC to later check the battery voltage or internal temperature.
82  *
83  * @param[in] peripheral
84  * Select the ADC peripheral to initialize.
85  *****************************************************************************/
86 void initADC (ADC_Measurement_t peripheral)
87 {
88  /* Enable necessary clocks (just in case) */
89  CMU_ClockEnable(cmuClock_HFPER, true); /* ADC0 is a High Frequency Peripheral */
90  CMU_ClockEnable(cmuClock_ADC0, true);
91 
92  /* Set a timebase providing at least 1 us.
93  * If the argument is "0" the currently defined HFPER clock setting is used. */
94  init.timebase = ADC_TimebaseCalc(0);
95 
96  /* Set a prescale value according to the ADC frequency (400 000 Hz) wanted.
97  * If the last argument is "0" the currently defined HFPER clock setting is for the calculation used. */
98  init.prescale = ADC_PrescaleCalc(400000, 0);
99 
100  /* Initialize ADC peripheral */
101  ADC_Init(ADC0, &init);
102 
103  /* Setup single conversions */
104 
105  /* initSingle.acqTime = adcAcqTime16;
106  * The statement above was found in a SiLabs example but DRAMCO disabled it.
107  * After testing this seemed to have no real effect so it was disabled.
108  * This is probably not necessary since a prescale value other than 0 (default) has been defined. */
109  if (peripheral == INTERNAL_TEMPERATURE) initSingle.input = adcSingleInpTemp; /* Internal temperature */
110  else if (peripheral == BATTERY_VOLTAGE) initSingle.input = adcSingleInpVDDDiv3; /* Internal VDD/3 */
111  else
112  {
113 
114 #if DEBUG_DBPRINT == 1 /* DEBUG_DBPRINT */
115  dbcrit("Unknown ADC peripheral selected!");
116 #endif /* DEBUG_DBPRINT */
117 
118  /* Disable used clock */
119  CMU_ClockEnable(cmuClock_ADC0, false);
120 
121  error(11);
122 
123  /* Exit function */
124  return;
125  }
126 
127  ADC_InitSingle(ADC0, &initSingle);
128 
129  /* Manually set some calibration values
130  * ADC0->CAL = (0x7C << _ADC_CAL_SINGLEOFFSET_SHIFT) | (0x1F << _ADC_CAL_SINGLEGAIN_SHIFT);
131  * The statement above was found in a SiLabs example but DRAMCO disabled it.
132  * After testing this seemed to throw off the first measurement so it was disabled. */
133 
134  /* Enable interrupt on completed conversion */
135  ADC_IntEnable(ADC0, ADC_IEN_SINGLE);
136  NVIC_ClearPendingIRQ(ADC0_IRQn);
137  NVIC_EnableIRQ(ADC0_IRQn);
138 
139  /* Disable used clock */
140  CMU_ClockEnable(cmuClock_ADC0, false);
141 
142 #if DEBUG_DBPRINT == 1 /* DEBUG_DBPRINT */
143  if (peripheral == INTERNAL_TEMPERATURE) dbinfo("ADC0 initialized for internal temperature");
144  else if (peripheral == BATTERY_VOLTAGE) dbinfo("ADC0 initialized for VBAT");
145 #endif /* DEBUG_DBPRINT */
146 
147 }
148 
149 
150 /**************************************************************************//**
151  * @brief
152  * Method to read the battery voltage or internal temperature.
153  *
154  * @details
155  * This method re-initializes the ADC settings if necessary.@n
156  * **Negative internal temperatures work fine.**
157  *
158  * @param[in] peripheral
159  * Select the ADC peripheral to read from.
160  *
161  * @return
162  * The measured battery voltage or internal temperature.
163  *****************************************************************************/
164 int32_t readADC (ADC_Measurement_t peripheral)
165 {
166  uint16_t counter = 0; /* Timeout counter */
167  int32_t value = 0; /* Value to eventually return */
168 
169  /* Enable necessary clock */
170  CMU_ClockEnable(cmuClock_ADC0, true);
171 
172  /* Change ADC settings if necessary */
173  if (peripheral == INTERNAL_TEMPERATURE)
174  {
175  if (initSingle.input != adcSingleInpTemp)
176  {
177  initSingle.input = adcSingleInpTemp; /* Internal temperature */
178  ADC_InitSingle(ADC0, &initSingle);
179  }
180  }
181  else if (peripheral == BATTERY_VOLTAGE)
182  {
183  if (initSingle.input != adcSingleInpVDDDiv3)
184  {
185  initSingle.input = adcSingleInpVDDDiv3; /* Internal VDD/3 */
186  ADC_InitSingle(ADC0, &initSingle);
187  }
188  }
189  else
190  {
191 
192 #if DEBUG_DBPRINT == 1 /* DEBUG_DBPRINT */
193  dbcrit("Unknown ADC peripheral selected!");
194 #endif /* DEBUG_DBPRINT */
195 
196  /* Disable used clock */
197  CMU_ClockEnable(cmuClock_ADC0, false);
198 
199  error(12);
200 
201  /* Exit function */
202  return (0);
203  }
204 
205  /* Set variable false just in case */
206  adcConversionComplete = false;
207 
208  /* Start single ADC conversion */
209  ADC_Start(ADC0, adcStartSingle);
210 
211  /* Wait until the conversion is completed */
212  while ((counter < TIMEOUT_CONVERSION) && !adcConversionComplete) counter++;
213 
214  /* Exit the function if the maximum waiting time was reached */
215  if (counter == TIMEOUT_CONVERSION)
216  {
217 
218 #if DEBUG_DBPRINT == 1 /* DEBUG_DBPRINT */
219  dbcrit("Waiting time for ADC conversion reached!");
220 #endif /* DEBUG_DBPRINT */
221 
222  /* Disable used clock */
223  CMU_ClockEnable(cmuClock_ADC0, false);
224 
225  error(13);
226 
227  /* Exit function */
228  return (0);
229  }
230 #if DBPRINT_TIMEOUT == 1 /* DBPRINT_TIMEOUT */
231  else
232  {
233 
234 #if DEBUG_DBPRINT == 1 /* DEBUG_DBPRINT */
235  dbwarnInt("ADC conversion (", counter, ")");
236 #endif /* DEBUG_DBPRINT */
237 
238  }
239 #endif /* DBPRINT_TIMEOUT */
240 
241  /* Get the ADC value */
242  value = ADC_DataSingleGet(ADC0);
243 
244  /* Disable used clock */
245  CMU_ClockEnable(cmuClock_ADC0, false);
246 
247  /* Calculate final value according to parameter */
248  if (peripheral == INTERNAL_TEMPERATURE)
249  {
250  float32_t ft = convertToCelsius(value)*1000;
251  value = (int32_t) ft;
252  }
253  else if (peripheral == BATTERY_VOLTAGE)
254  {
255  float32_t fv = value * 3.75 / 4.096;
256  value = (int32_t) fv;
257  }
258 
259  return (value);
260 }
261 
262 
263  /**************************************************************************//**
264  * @brief
265  * Method to convert an ADC value to a temperature value.
266  *
267  * @note
268  * This is a static method because it's only internally used in this file
269  * and called by other methods if necessary.
270  *
271  * @param[in] adcSample
272  * The ADC sample to convert to a temperature value.
273  *
274  * @return
275  * The converted temperature value.
276  *****************************************************************************/
277 static float32_t convertToCelsius (int32_t adcSample)
278 {
279  float32_t temp;
280 
281  /* Factory calibration temperature from device information page. */
282  int32_t cal_temp_0 = ((DEVINFO->CAL & _DEVINFO_CAL_TEMP_MASK)
283  >> _DEVINFO_CAL_TEMP_SHIFT);
284 
285  /* Factory calibration value from device information page. */
286  int32_t cal_value_0 = ((DEVINFO->ADC0CAL2 & _DEVINFO_ADC0CAL2_TEMP1V25_MASK)
287  >> _DEVINFO_ADC0CAL2_TEMP1V25_SHIFT);
288 
289  /* Temperature gradient (from datasheet) */
290  float32_t t_grad = -6.27;
291 
292  temp = (cal_temp_0 - ((cal_value_0 - adcSample) / t_grad));
293 
294  return (temp);
295 }
296 
297 
298 /**************************************************************************//**
299  * @brief
300  * Interrupt Service Routine for ADC0.
301  *****************************************************************************/
302 void ADC0_IRQHandler (void)
303 {
304  /* Read interrupt flags */
305  uint32_t flags = ADC_IntGet(ADC0);
306 
307  /* Clear the ADC0 interrupt flags */
308  ADC_IntClear(ADC0, flags);
309 
310  /* Indicate that an ADC conversion has been completed */
311  adcConversionComplete = true;
312 }
ADC_InitSingle_TypeDef initSingle
Definition: adc.c:72
void ADC0_IRQHandler(void)
Interrupt Service Routine for ADC0.
Definition: adc.c:302
void dbwarnInt(char *message1, int32_t value, char *message2)
Print a warning value surrounded by two strings (char array) to USARTx.
Definition: dbprint.c:598
ADC functionality for reading the (battery) voltage and internal temperature.
#define TIMEOUT_CONVERSION
Definition: adc.c:66
void dbinfo(char *message)
Print an info string (char array) to USARTx and go to the next line.
Definition: dbprint.c:503
int32_t readADC(ADC_Measurement_t peripheral)
Method to read the battery voltage or internal temperature.
Definition: adc.c:164
static float32_t convertToCelsius(int32_t adcSample)
Method to convert an ADC value to a temperature value.
Definition: adc.c:277
ADC_Init_TypeDef init
Definition: adc.c:71
void error(uint8_t number)
Error method.
Definition: util.c:131
volatile bool adcConversionComplete
Definition: adc.c:70
Utility functionality.
Enable or disable printing to UART with dbprint.
void initADC(ADC_Measurement_t peripheral)
Method to initialize the ADC to later check the battery voltage or internal temperature.
Definition: adc.c:86
void dbcrit(char *message)
Print a critical error string (char array) in red to USARTx and go to the next line.
Definition: dbprint.c:539
enum adc_measurements ADC_Measurement_t