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