Commit mthomas changes to GCC and port those changes to IAR
[avr_bc100.git] / BaseTinyFirmware / GCC / ADC.c
1 /* This file has been prepared for Doxygen automatic documentation generation.*/\r
2 /*! \file *********************************************************************\r
3  *\r
4  * \brief\r
5  *      Functions for use of ADC\r
6  *\r
7  *      Contains high level functions for initializing the ADC, interrupt\r
8  *      handling, and treatment of samples.\n\r
9  *      The ADC is set to free running mode and uses an external reference\r
10  *      voltage.\n\r
11  *      To make all sampling take at least 25 clock cycles the ADC is stopped\r
12  *      and restarted by the ISR.\r
13  *\r
14  * \par Application note:\r
15  *      AVR458: Charging Li-Ion Batteries with BC100 \n\r
16  *      AVR463: Charging NiMH Batteries with BC100\r
17  *\r
18  * \par Documentation:\r
19  *      For comprehensive code documentation, supported compilers, compiler\r
20  *      settings and supported devices see readme.html\r
21  *\r
22  * \author\r
23  *      Atmel Corporation: http://www.atmel.com \n\r
24  *      Support email: avr@atmel.com \n\r
25  *      Original author: \n\r
26  *\r
27  * $Name$\r
28  * $Revision: 2299 $\r
29  * $RCSfile$\r
30  * $URL: http://svn.norway.atmel.com/AppsAVR8/avr458_Charging_Li-Ion_Batteries_with_BC100/tag/20070904_release_1.0/code/IAR/ADC.c $\r
31  * $Date: 2007-08-23 12:55:51 +0200 (to, 23 aug 2007) $\n\r
32  ******************************************************************************/\r
33 \r
34 #include <avr/io.h>\r
35 #include <avr/interrupt.h>\r
36 \r
37 #include "structs.h"\r
38 \r
39 #include "main.h"\r
40 #include "ADC.h"\r
41 \r
42 \r
43 //******************************************************************************\r
44 // Variables\r
45 //******************************************************************************\r
46 // ADC status struct.\r
47 //! \brief Holds sampled data and ADC-status\r
48 volatile ADC_Status_t ADCS;\r
49 \r
50 \r
51 /*! \brief Indicates maximum battery voltage.\r
52  *\r
53  * This variable is stored in EEPROM and indicates how much the battery voltage\r
54  * is downscaled by HW before it is sampled. The amount of downscaling depends\r
55  * on the maximum battery voltage, and is necessary to avoid saturation of the\r
56  * ADC (reference voltage is 2.5 V).\r
57  *\r
58  * \note Used by the ADC ISR when calling ScaleU() and ScaleI().\r
59  *\r
60  * \note Defaults to 1, which means 10 V max battery voltage.\r
61  *\r
62  * \note Table of settings:\r
63  * <pre>\r
64  * VBAT_RANGE | Max battery voltage | Jumper setting\r
65  *         0  |             5V      |        1/2\r
66  *         1  |            10V      |        1/4\r
67  *         2  |            20V      |        1/8\r
68  *         3  |            30V      |       1/12\r
69  *         4  |            40V      |       1/16\r
70  * </pre>\r
71  */\r
72 // Maximum battery voltage (affects scaling of samples).\r
73 unsigned char EEMEM VBAT_RANGE = 1;\r
74 \r
75 \r
76 //******************************************************************************\r
77 // Functions\r
78 //******************************************************************************\r
79 /*! \brief Interrupt Service routine for ADC.\r
80  *\r
81  * This ISR stores the sampled values in the ADC status-struct, then\r
82  * updates the ADC MUX to the next channel in the scanning-sequence.\n\r
83  * Once the sequence is completed, ADCS.Flag is set and unless\r
84  * ADCS.Halt has been set, the sequence starts over. Otherwise, the ADC\r
85  * is disabled.\n\r
86  * If the mains voltage is below minimum, ADCS.Mains gets set to FALSE.\r
87  *\r
88  * \note Table of scanning sequence:\r
89  * <pre>\r
90  * Seq |    MUX |  pos I/P |  neg I/P | gain | measure | signed\r
91  * ----+--------+----------+----------+------+---------+-------\r
92  *  01 | 000001 | ADC1/PA1 |      n/a |   1x |     NTC |     no\r
93  *  02 | 000010 | ADC2/PA2 |      n/a |   1x |     RID |     no\r
94  *  03 | 000011 | ADC3/PA4 |      n/a |   1x |    VIN- |     no\r
95  *  04 | 000100 | ADC4/PA5 |      n/a |   1x |    VIN+ |     no\r
96  *  05 | 000101 | ADC5/PA6 |      n/a |   1x |   VBAT- |     no\r
97  *  06 | 000110 | ADC6/PA7 |      n/a |   1x |   VBAT+ |     no\r
98  *  07 | 010010 | ADC4/PA5 | ADC3/PA4 |  20x |     IIN |     no\r
99  *  08 | 010111 | ADC6/PA7 | ADC5/PA6 |  20x |    IBAT |    yes\r
100  * </pre>\r
101  *\r
102  * \todo IIN (#7 in sequence) is never used.\r
103  *\r
104  * \todo Signed is never set. Signed measurements of IBAT will halve the\r
105  * measuring sensitivity, and is therefore not favourable. At the moment,\r
106  * great currents (f.ex. if something happens with the battery) will be\r
107  * interpreted as negative, which might cause unfavourable behaviour during\r
108  * charging (depending on what PWM behaviour is defined), f.ex.\r
109  * ConstantCurrent() will keep increasing the PWM output. This results in an\r
110  * PWM controller error being flagged and the program going into\r
111  * error-state and eventually reinitializing.\r
112  */\r
113 ISR(ADC_vect)\r
114 {\r
115         static unsigned char avgIndex = 0;\r
116         unsigned char i, Next, Signed;\r
117         signed int  temp = 0;\r
118 \r
119         Signed = FALSE;  // Presume next conversion is unipolar.\r
120         ADCSRA &= ~(1<<ADEN);  // Stop conversion before handling. This makes all\r
121           // conversions take at least 25 ADCCLK. (It is restarted later)\r
122 \r
123         // Handle the conversion, depending on what channel it is from, then\r
124         // switch to the next channel in the sequence.\r
125         switch (ADCS.MUX){\r
126                 // MUX = 0b000001 => ADC1 (PA1) = NTC\r
127                 case 0x01:\r
128                         ADCS.rawNTC = ADC;\r
129                         Next=0x02;\r
130                 break;\r
131 \r
132 \r
133                 // MUX = 0b000010 => ADC2 (PA2) = RID\r
134                 case 0x02:\r
135                         ADCS.rawRID = ADC;\r
136                         Next=0x03;\r
137                 break;\r
138 \r
139 \r
140                 // MUX = 0b000011 => ADC3 (PA4) = VIN-\r
141                 case 0x03:\r
142                         // Supply voltage is always divided by 16.\r
143                         ADCS.VIN = ScaleU(4, (unsigned int)ADC);  // Cast because ADC is short.\r
144 \r
145                         // Is mains failing?\r
146                         if (ADCS.VIN < VIN_MIN) {\r
147                                 ADCS.Mains = FALSE;\r
148                         } else {\r
149                                 ADCS.Mains = TRUE;\r
150                         }\r
151 \r
152                         Next=0x05;\r
153                 break;\r
154 \r
155 \r
156                 // MUX = 0b000101 => ADC5 (PA6) = VBAT-\r
157                 case 0x05:\r
158                         ADCS.rawVBAT = ADC;\r
159 \r
160                         // Scale voltage according to jumper setting.\r
161                         ADCS.VBAT = ScaleU(eeprom_read_byte(&VBAT_RANGE), (unsigned int)ADC); // ADC is a short.\r
162                         __asm__ __volatile__ ("NOP"::);\r
163                         Next=0x17;\r
164 //                      Signed = TRUE;  // Next conversion is bipolar. Halves sensitivity!\r
165                 break;\r
166 \r
167 \r
168                 case 0x17:  // MUX = 0b010111 => 20 x [ADC6(PA7) - ADC5(PA6)] = IBAT\r
169                         // If bipolar, from -512 to 0, to 511:\r
170                         // 0x200 ... 0x3ff, 0x000, 0x001 ... 0x1FF\r
171 \r
172                         // Scale sample according to jumper setting, handle negative numbers.\r
173                         if (ADC > 511) {\r
174                                 ADCS.IBAT = -(signed int)ScaleI(eeprom_read_byte(&VBAT_RANGE),\r
175                                                                 (1024 - (ADC-ADCS.ADC5_G20_OS)));\r
176                         } else if (ADC > 0) {\r
177                                 ADCS.IBAT = ScaleI(eeprom_read_byte(&VBAT_RANGE), (ADC-ADCS.ADC5_G20_OS));\r
178                         } else {\r
179                                 ADCS.IBAT = 0;\r
180                         }\r
181 \r
182                         // Insert sample of battery current into the averaging-array\r
183                         // (overwriting the oldest sample), then recalculate and store the\r
184                         // average. This is the last conversion in the sequence, so\r
185                         // flag a complete ADC-cycle and restart sequence.\r
186                         ADCS.discIBAT[(avgIndex++ & 0x03)] = ADCS.IBAT;\r
187                         for (i = 0; i < 4 ; i++) {\r
188                                 temp += ADCS.discIBAT[i];\r
189                         }\r
190 \r
191                         ADCS.avgIBAT = (temp / 4);\r
192 \r
193                         ADCS.Flag = TRUE;\r
194                         Next=0x01;\r
195                         Signed = FALSE;  // This is the only bipolar conversion.\r
196                 break;\r
197 \r
198 \r
199                 default:  // Should not happen. (Invalid MUX-channel)\r
200                         Next=0x01;  // Start at the beginning of sequence.\r
201                 break;\r
202         }\r
203 \r
204         // Update MUX to next channel in sequence, set a bipolar conversion if\r
205         // this has been flagged.\r
206         ADCS.MUX = Next;\r
207         ADMUX = (1<<REFS0) + ADCS.MUX;\r
208 \r
209         if (Signed)     {\r
210                 ADCSRB |= (1<<BIN);\r
211         } else {\r
212                 ADCSRB &= ~(1<<BIN);\r
213         }\r
214 \r
215         // Re-enable the ADC unless a halt has been flagged and a conversion\r
216         // cycle has completed.\r
217         if (!((ADCS.Halt) && (ADCS.Flag))) {\r
218                 ADCSRA |= (1<<ADEN)|(1<<ADSC);\r
219         }\r
220 }\r
221 \r
222 \r
223 /*! \brief Scales sample to represent "actual voltage" in mV.\r
224  *\r
225  * This function returns the actual sampled voltage, scaled according\r
226  * to the jumper settings.\r
227  *\r
228  * \param setting Indicates what downscaling was used.\r
229  * \param data The sampled value.\r
230  *\r
231  * \note Table for setting-parameter:\n\r
232  * <pre>\r
233  * Presume VREF = 2.5V and Gain = 1x.\r
234  * => Resolution @ 1/1 = 2.5V / 1024 = 2.4414 mV/LSB\r
235  * setting | source |   R1 | R2/(R1+R2) | UADC(LSB) | U(MAX)\r
236  * --------+--------+------+------------+-----------+-------\r
237  *     N/A |        |    - |       -    |   2.441mV |  2.50V\r
238  *       0 |   VBAT |  10k |     1/2    |   4.883mV |  5.00V\r
239  *       1 |   VBAT |  30k |     1/4    |   9.766mV |  9.99V\r
240  *       2 |   VBAT |  70k |     1/8    |   19.53mV | 19.98V\r
241  *       3 |   VBAT | 110k |    1/12    |   29.30mV | 29.97V\r
242  *       4 |   VBAT | 150k |    1/16    |   39.06mV | 39.96V\r
243  *       4 |    VIN | 150k |    1/16    |   39.06mV | 39.96V\r
244  * </pre>\r
245  */\r
246 unsigned int ScaleU(unsigned char setting, unsigned int data)\r
247 {\r
248         // Temporary variable needed.\r
249         unsigned int scaled = 0;\r
250 \r
251         // Jumper setting 3: mV/LSB = 29.30 ~= 29 + 1/4 + 1/16\r
252         if (setting == 3)       {\r
253                 scaled = 29 * data;\r
254                 scaled += (data >> 2);\r
255                 scaled += (data >> 4);\r
256         } else {\r
257                 // Jumper setting 4: mV/LSB = 39.06 ~= 39 + 1/16\r
258                 scaled = 39 * data;\r
259                 scaled += (data >> 4);\r
260 \r
261                 if (setting <3) {\r
262                         // Jumper setting 0: mV/LSB = 4.883 = 39.06 / 8\r
263                         //                1: mV/LSB = 9.766 = 39.06 / 4\r
264                         //                2: mV/LSB = 19.53 = 39.06 / 2\r
265                         scaled = (scaled >> (3-setting));\r
266                 }\r
267         }\r
268 \r
269         return(scaled);\r
270 }\r
271 \r
272 \r
273 /*! \brief Scales sample to represent "actual current" in mA.\r
274  *\r
275  * This function returns the actual sampled current, scaled according\r
276  * to the jumper settings.\r
277  *\r
278  * \param setting Indicates what downscaling was used.\r
279  * \param data The sampled value.\r
280  *\r
281  * \note Table for setting-parameter:\n\r
282  * <pre>\r
283  * Presume VREF = 2.5V and Gain = 1x or 20x.\r
284  * => Resolution(U) @ (1/1 and 20x) = 2.5V / (GAIN x 1024) = 0.1221 mV/LSB\r
285  * => Resolution(I) = Resolution(U) / Rshunt = Resolution(U) / 0.07\r
286  * Setting |   R1 | R2/(R1+R2) |   U(LSB) |   I(LSB) | I(MAX) | Gain\r
287  * --------+------+------------+----------+----------+--------+-----\r
288  *     N/A |    - |       -    | 0.1221mV |  1.744mA |  1.78A |  20x\r
289  *       0 |  10k |     1/2    | 0.2442mV |  3.489mA |  3.57A |  20x\r
290  *       1 |  30k |     1/4    | 0.4884mV |  6.978mA |  7.14A |  20x\r
291  *       2 |  70k |     1/8    | 0.9768mV | 13.955mA |  14.3A |  20x\r
292  *       3 | 110k |    1/12    | 1.4652mV | 20.931mA |  21.4A |  20x\r
293  *       4 | 150k |    1/16    | 1.9536mV | 27.909mA |  28.5A |  20x\r
294  *       5 |  10k |     1/2    | 2.4414mV | 34.877mA |  35.7A |   1x\r
295  * </pre>\r
296  */\r
297 unsigned int ScaleI(unsigned char setting, unsigned int data)\r
298 {\r
299         // Temporary variable needed.\r
300         unsigned int  scaled = 0;\r
301 \r
302         // Jumper setting 3: mA/LSB = 20.931mA ~= 21 - 1/16 + 1/128\r
303         if (setting == 3) {\r
304                 scaled = 21 * data;\r
305                 scaled -= (data >> 4);\r
306                 scaled += (data >> 7);\r
307         }       else    { // Jumper setting 4: mA/LSB = 27.909mA ~= 28 - 1/8 + 1/32\r
308                 scaled = 28 * data;\r
309                 scaled -= (data >> 3);\r
310                 scaled += (data >> 5);\r
311 \r
312                 if (setting <3) {\r
313                         // Jumper setting 0: mA/LSB = 3.489mA = 27.909 / 8\r
314                         //                1: mA/LSB = 6.978mA = 27.909 / 4\r
315                         //                2: mA/LSB = 13.955mA = 27.909 / 2\r
316                         scaled = (scaled >> (3-setting));\r
317                 }\r
318         }\r
319 \r
320         return(scaled);\r
321 }\r
322 \r
323 \r
324 /*! \brief Waits for two full cycles of ADC-conversions to occur.\r
325  *\r
326  * This function clears the cycle complete-flag, then waits for it to be set\r
327  * again. This is then repeated once before the function exits.\r
328  *\r
329  */\r
330 void ADC_Wait(void)\r
331 {\r
332         // Clear ADC flag and wait for cycle to complete.\r
333         ADCS.Flag = FALSE;\r
334         do {\r
335         } while (ADCS.Flag == FALSE);\r
336 \r
337         // Repeat, so we are sure the data belongs to the same cycle.\r
338         ADCS.Flag = FALSE;\r
339         do {\r
340         } while (ADCS.Flag == FALSE);\r
341 }\r
342 \r
343 \r
344 /*! \brief Initializes ADC and input pins.\r
345  *\r
346  * This function initializes the ADC to free running mode, sampling from\r
347  * PA1/2/4/5/6/7, and using an external reference voltage (PA3).\n\r
348  * It also measures and stores calibration data for offset.\r
349  *\r
350  * \todo Odd offset measurement for ADC3_G20_OS? It is never used anyway.\r
351  *\r
352  * \note Table of MUX settings for offset measurement:\r
353  * <pre>\r
354  *    Ch | Pin |    Gain |    MUX\r
355  * ------+-----+---------+-------\r
356  *  ADC1 | PA1 |     20x | 001101\r
357  *  ADC3 | PA4 |     20x | 010001\r
358  *  ADC5 | PA6 |     20x | 010110\r
359  *  ADC9 | PB6 |     20x | 011011\r
360  *  ADC0 | PA0 | 20x/32x | 111000\r
361  *  ADC0 | PA0 |   1x/8x | 111001\r
362  *  ADC1 | PA1 | 20x/32x | 111010\r
363  *  ADC2 | PA2 | 20x/32x | 111011\r
364  *  ADC4 | PA5 | 20x/32x | 111100\r
365  *  ADC5 | PA6 | 20x/32x | 111101\r
366  *  ADC6 | PA7 | 20x/32x | 111110\r
367  * </pre>\r
368  */\r
369 void ADC_Init(void)\r
370 {\r
371         unsigned char i;\r
372         unsigned char sreg_saved;\r
373 \r
374         sreg_saved = SREG;\r
375         cli();\r
376 \r
377         ADCS.Halt = FALSE; // Enable consecutive runs of ADC.\r
378 \r
379         // Configure ADC pins (inputs and disabled pull-ups).\r
380         DDRA &= ~((1<<PA1)|(1<<PA2)|(1<<PA4)|(1<<PA5)|(1<<PA6)|(1<<PA7));\r
381         PORTA &= ~((1<<PA1)|(1<<PA2)|(1<<PA4)|(1<<PA5)|(1<<PA6)|(1<<PA7));\r
382 \r
383         // Set ADC3 as reference, and MUX to measure the same pin.\r
384         ADMUX = (1<<REFS0) | (1<<MUX0) | (1<<MUX1);\r
385 \r
386         ADCSRB = 0;\r
387 \r
388         // Start conversion, no interrupt (disable ADC-ISR).\r
389         ADCSRA = (1<<ADEN) | (1<<ADSC) | ADC_PRESCALER;\r
390 \r
391         do { // Wait for conversion to finish.\r
392         } while (!(ADCSRA & (1<<ADIF)));\r
393 \r
394         ADCSRA |= (1<<ADIF);  // Clear ADC interrupt flag manually.\r
395 \r
396         ADCS.ADC3_G20_OS = ADC;  // Save the sampled offset.\r
397 \r
398         ADMUX = (1<<REFS0) | 0x16;  // ADC5/ADC5 (external ref.), 20x\r
399 \r
400         // Start conversion, no interrupt. ADC_PRESCALER is defined in ADC.h.\r
401         ADCSRA = (1<<ADEN) | (1<<ADSC) | ADC_PRESCALER;\r
402 \r
403         do { // Wait for conversion to finish.\r
404         } while (!(ADCSRA & (1<<ADIF)));\r
405 \r
406         ADCSRA |= (1<<ADIF);  // Clear ADC interrupt flag.\r
407 \r
408         ADCS.ADC5_G20_OS = ADC;  // Save the sampled offset.\r
409 \r
410         // Reset the ADC-cycle.\r
411         ADCS.Flag = FALSE;\r
412         ADCS.MUX = 0x01;\r
413         ADMUX = (1<<REFS0) | ADCS.MUX;\r
414 \r
415         // Clear averaged battery current and the discrete readings.\r
416         ADCS.avgIBAT = 0;\r
417 \r
418         for (i = 0; i < 4; i++) {\r
419                 ADCS.discIBAT[i] = 0;\r
420         }\r
421 \r
422         // Re-enable the ADC and ISR.\r
423         ADCSRA=(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|ADC_PRESCALER;\r
424 \r
425         sei();\r
426 \r
427         // Get a complete cycle of data before returning.\r
428         ADC_Wait();\r
429 \r
430         SREG = sreg_saved;\r
431 }\r