1 /* This file has been prepared for Doxygen automatic documentation generation.*/
\r
2 /*! \file *********************************************************************
\r
5 * Functions for use of ADC
\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
11 * To make all sampling take at least 25 clock cycles the ADC is stopped
\r
12 * and restarted by the ISR.
\r
14 * \par Application note:
\r
15 * AVR458: Charging Li-Ion Batteries with BC100 \n
\r
16 * AVR463: Charging NiMH Batteries with BC100
\r
18 * \par Documentation:
\r
19 * For comprehensive code documentation, supported compilers, compiler
\r
20 * settings and supported devices see readme.html
\r
23 * Atmel Corporation: http://www.atmel.com \n
\r
24 * Support email: avr@atmel.com \n
\r
25 * Original author: \n
\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
36 #include <intrinsics.h>
\r
38 #include "structs.h"
\r
44 //******************************************************************************
\r
46 //******************************************************************************
\r
47 // ADC status struct.
\r
48 //! \brief Holds sampled data and ADC-status
\r
52 /*! \brief Indicates maximum battery voltage.
\r
54 * This variable is stored in EEPROM and indicates how much the battery voltage
\r
55 * is downscaled by HW before it is sampled. The amount of downscaling depends
\r
56 * on the maximum battery voltage, and is necessary to avoid saturation of the
\r
57 * ADC (reference voltage is 2.5 V).
\r
59 * \note Used by the ADC ISR when calling ScaleU() and ScaleI().
\r
61 * \note Defaults to 1, which means 10 V max battery voltage.
\r
63 * \note Table of settings:
\r
65 * VBAT_RANGE | Max battery voltage | Jumper setting
\r
73 // Maximum battery voltage (affects scaling of samples).
\r
74 __eeprom unsigned char VBAT_RANGE = 1;
\r
77 //******************************************************************************
\r
79 //******************************************************************************
\r
80 /*! \brief Interrupt Service routine for ADC.
\r
82 * This ISR stores the sampled values in the ADC status-struct, then
\r
83 * updates the ADC MUX to the next channel in the scanning-sequence.\n
\r
84 * Once the sequence is completed, ADCS.Flag is set and unless
\r
85 * ADCS.Halt has been set, the sequence starts over. Otherwise, the ADC
\r
87 * If the mains voltage is below minimum, ADCS.Mains gets set to FALSE.
\r
89 * \note Table of scanning sequence:
\r
91 * Seq | MUX | pos I/P | neg I/P | gain | measure | signed
\r
92 * ----+--------+----------+----------+------+---------+-------
\r
93 * 01 | 000001 | ADC1/PA1 | n/a | 1x | NTC | no
\r
94 * 02 | 000010 | ADC2/PA2 | n/a | 1x | RID | no
\r
95 * 03 | 000011 | ADC3/PA4 | n/a | 1x | VIN- | no
\r
96 * 04 | 000100 | ADC4/PA5 | n/a | 1x | VIN+ | no
\r
97 * 05 | 000101 | ADC5/PA6 | n/a | 1x | VBAT- | no
\r
98 * 06 | 000110 | ADC6/PA7 | n/a | 1x | VBAT+ | no
\r
99 * 07 | 010010 | ADC4/PA5 | ADC3/PA4 | 20x | IIN | no
\r
100 * 08 | 010111 | ADC6/PA7 | ADC5/PA6 | 20x | IBAT | yes
\r
103 * \todo IIN (#7 in sequence) is never used.
\r
105 * \todo Signed is never set. Signed measurements of IBAT will halve the
\r
106 * measuring sensitivity, and is therefore not favourable. At the moment,
\r
107 * great currents (f.ex. if something happens with the battery) will be
\r
108 * interpreted as negative, which might cause unfavourable behaviour during
\r
109 * charging (depending on what PWM behaviour is defined), f.ex.
\r
110 * ConstantCurrent() will keep increasing the PWM output. This results in an
\r
111 * PWM controller error being flagged and the program going into
\r
112 * error-state and eventually reinitializing.
\r
114 #pragma vector=ADC_vect
\r
115 __interrupt void ADC_ISR(void)
\r
117 static unsigned char avgIndex = 0;
\r
118 unsigned char i, Next, Signed;
\r
119 signed int temp = 0;
\r
121 Signed = FALSE; // Presume next conversion is unipolar.
\r
122 ADCSRA &= ~(1<<ADEN); // Stop conversion before handling. This makes all
\r
123 // conversions take at least 25 ADCCLK. (It is restarted later)
\r
125 // Handle the conversion, depending on what channel it is from, then
\r
126 // switch to the next channel in the sequence.
\r
128 // MUX = 0b000001 => ADC1 (PA1) = NTC
\r
135 // MUX = 0b000010 => ADC2 (PA2) = RID
\r
142 // MUX = 0b000011 => ADC3 (PA4) = VIN-
\r
144 // Supply voltage is always divided by 16.
\r
145 ADCS.VIN = ScaleU(4, (unsigned int)ADC); // Cast because ADC is short.
\r
147 // Is mains failing?
\r
148 if (ADCS.VIN < VIN_MIN) {
\r
149 ADCS.Mains = FALSE;
\r
158 // MUX = 0b000101 => ADC5 (PA6) = VBAT-
\r
160 ADCS.rawVBAT = ADC;
\r
162 // Scale voltage according to jumper setting.
\r
163 ADCS.VBAT = ScaleU(VBAT_RANGE, (unsigned int)ADC); // ADC is a short.
\r
166 // Signed = TRUE; // Next conversion is bipolar. Halves sensitivity!
\r
170 case 0x17: // MUX = 0b010111 => 20 x [ADC6(PA7) - ADC5(PA6)] = IBAT
\r
171 // If bipolar, from -512 to 0, to 511:
\r
172 // 0x200 ... 0x3ff, 0x000, 0x001 ... 0x1FF
\r
174 // Scale sample according to jumper setting, handle negative numbers.
\r
176 ADCS.IBAT = -(signed int)ScaleI(VBAT_RANGE,
\r
177 (1024 - (ADC-ADCS.ADC5_G20_OS)));
\r
178 } else if (ADC > 0) {
\r
179 ADCS.IBAT = ScaleI(VBAT_RANGE, (ADC-ADCS.ADC5_G20_OS));
\r
184 // Insert sample of battery current into the averaging-array
\r
185 // (overwriting the oldest sample), then recalculate and store the
\r
186 // average. This is the last conversion in the sequence, so
\r
187 // flag a complete ADC-cycle and restart sequence.
\r
188 ADCS.discIBAT[(avgIndex++ & 0x03)] = ADCS.IBAT;
\r
189 for (i = 0; i < 4 ; i++) {
\r
190 temp += ADCS.discIBAT[i];
\r
193 ADCS.avgIBAT = (temp / 4);
\r
197 Signed = FALSE; // This is the only bipolar conversion.
\r
201 default: // Should not happen. (Invalid MUX-channel)
\r
202 Next=0x01; // Start at the beginning of sequence.
\r
206 // Update MUX to next channel in sequence, set a bipolar conversion if
\r
207 // this has been flagged.
\r
209 ADMUX = (1<<REFS0) + ADCS.MUX;
\r
212 ADCSRB |= (1<<BIN);
\r
214 ADCSRB &= ~(1<<BIN);
\r
217 // Re-enable the ADC unless a halt has been flagged and a conversion
\r
218 // cycle has completed.
\r
219 if (!((ADCS.Halt) && (ADCS.Flag))) {
\r
220 ADCSRA |= (1<<ADEN)|(1<<ADSC);
\r
225 /*! \brief Scales sample to represent "actual voltage" in mV.
\r
227 * This function returns the actual sampled voltage, scaled according
\r
228 * to the jumper settings.
\r
230 * \param setting Indicates what downscaling was used.
\r
231 * \param data The sampled value.
\r
233 * \note Table for setting-parameter:\n
\r
235 * Presume VREF = 2.5V and Gain = 1x.
\r
236 * => Resolution @ 1/1 = 2.5V / 1024 = 2.4414 mV/LSB
\r
237 * setting | source | R1 | R2/(R1+R2) | UADC(LSB) | U(MAX)
\r
238 * --------+--------+------+------------+-----------+-------
\r
239 * N/A | | - | - | 2.441mV | 2.50V
\r
240 * 0 | VBAT | 10k | 1/2 | 4.883mV | 5.00V
\r
241 * 1 | VBAT | 30k | 1/4 | 9.766mV | 9.99V
\r
242 * 2 | VBAT | 70k | 1/8 | 19.53mV | 19.98V
\r
243 * 3 | VBAT | 110k | 1/12 | 29.30mV | 29.97V
\r
244 * 4 | VBAT | 150k | 1/16 | 39.06mV | 39.96V
\r
245 * 4 | VIN | 150k | 1/16 | 39.06mV | 39.96V
\r
248 unsigned int ScaleU(unsigned char setting, unsigned int data)
\r
250 // Temporary variable needed.
\r
251 unsigned int scaled = 0;
\r
253 // Jumper setting 3: mV/LSB = 29.30 ~= 29 + 1/4 + 1/16
\r
254 if (setting == 3) {
\r
255 scaled = 29 * data;
\r
256 scaled += (data >> 2);
\r
257 scaled += (data >> 4);
\r
259 // Jumper setting 4: mV/LSB = 39.06 ~= 39 + 1/16
\r
260 scaled = 39 * data;
\r
261 scaled += (data >> 4);
\r
264 // Jumper setting 0: mV/LSB = 4.883 = 39.06 / 8
\r
265 // 1: mV/LSB = 9.766 = 39.06 / 4
\r
266 // 2: mV/LSB = 19.53 = 39.06 / 2
\r
267 scaled = (scaled >> (3-setting));
\r
275 /*! \brief Scales sample to represent "actual current" in mA.
\r
277 * This function returns the actual sampled current, scaled according
\r
278 * to the jumper settings.
\r
280 * \param setting Indicates what downscaling was used.
\r
281 * \param data The sampled value.
\r
283 * \note Table for setting-parameter:\n
\r
285 * Presume VREF = 2.5V and Gain = 1x or 20x.
\r
286 * => Resolution(U) @ (1/1 and 20x) = 2.5V / (GAIN x 1024) = 0.1221 mV/LSB
\r
287 * => Resolution(I) = Resolution(U) / Rshunt = Resolution(U) / 0.07
\r
288 * Setting | R1 | R2/(R1+R2) | U(LSB) | I(LSB) | I(MAX) | Gain
\r
289 * --------+------+------------+----------+----------+--------+-----
\r
290 * N/A | - | - | 0.1221mV | 1.744mA | 1.78A | 20x
\r
291 * 0 | 10k | 1/2 | 0.2442mV | 3.489mA | 3.57A | 20x
\r
292 * 1 | 30k | 1/4 | 0.4884mV | 6.978mA | 7.14A | 20x
\r
293 * 2 | 70k | 1/8 | 0.9768mV | 13.955mA | 14.3A | 20x
\r
294 * 3 | 110k | 1/12 | 1.4652mV | 20.931mA | 21.4A | 20x
\r
295 * 4 | 150k | 1/16 | 1.9536mV | 27.909mA | 28.5A | 20x
\r
296 * 5 | 10k | 1/2 | 2.4414mV | 34.877mA | 35.7A | 1x
\r
299 unsigned int ScaleI(unsigned char setting, unsigned int data)
\r
301 // Temporary variable needed.
\r
302 unsigned int scaled = 0;
\r
304 // Jumper setting 3: mA/LSB = 20.931mA ~= 21 - 1/16 + 1/128
\r
305 if (setting == 3) {
\r
306 scaled = 21 * data;
\r
307 scaled -= (data >> 4);
\r
308 scaled += (data >> 7);
\r
309 } else { // Jumper setting 4: mA/LSB = 27.909mA ~= 28 - 1/8 + 1/32
\r
310 scaled = 28 * data;
\r
311 scaled -= (data >> 3);
\r
312 scaled += (data >> 5);
\r
315 // Jumper setting 0: mA/LSB = 3.489mA = 27.909 / 8
\r
316 // 1: mA/LSB = 6.978mA = 27.909 / 4
\r
317 // 2: mA/LSB = 13.955mA = 27.909 / 2
\r
318 scaled = (scaled >> (3-setting));
\r
326 /*! \brief Waits for two full cycles of ADC-conversions to occur.
\r
328 * This function clears the cycle complete-flag, then waits for it to be set
\r
329 * again. This is then repeated once before the function exits.
\r
332 void ADC_Wait(void)
\r
334 // Clear ADC flag and wait for cycle to complete.
\r
337 } while (ADCS.Flag == FALSE);
\r
339 // Repeat, so we are sure the data beong to the same cycle.
\r
342 } while (ADCS.Flag == FALSE);
\r
346 /*! \brief Initializes ADC and input pins.
\r
348 * This function initializes the ADC to free running mode, sampling from
\r
349 * PA1/2/4/5/6/7, and using an external reference voltage (PA3).\n
\r
350 * It also measures and stores calibration data for offset.
\r
352 * \todo Odd offset measurement for ADC3_G20_OS? It is never used anyway.
\r
354 * \note Table of MUX settings for offset measurement:
\r
356 * Ch | Pin | Gain | MUX
\r
357 * ------+-----+---------+-------
\r
358 * ADC1 | PA1 | 20x | 001101
\r
359 * ADC3 | PA4 | 20x | 010001
\r
360 * ADC5 | PA6 | 20x | 010110
\r
361 * ADC9 | PB6 | 20x | 011011
\r
362 * ADC0 | PA0 | 20x/32x | 111000
\r
363 * ADC0 | PA0 | 1x/8x | 111001
\r
364 * ADC1 | PA1 | 20x/32x | 111010
\r
365 * ADC2 | PA2 | 20x/32x | 111011
\r
366 * ADC4 | PA5 | 20x/32x | 111100
\r
367 * ADC5 | PA6 | 20x/32x | 111101
\r
368 * ADC6 | PA7 | 20x/32x | 111110
\r
371 void ADC_Init(void)
\r
375 __disable_interrupt();
\r
377 ADCS.Halt = FALSE; // Enable consecutive runs of ADC.
\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
383 // Set ADC3 as reference, and MUX to measure the same pin.
\r
384 ADMUX = (1<<REFS0) | (1<<MUX0) | (1<<MUX1);
\r
388 // Start conversion, no interrupt (disable ADC-ISR).
\r
389 ADCSRA = (1<<ADEN) | (1<<ADSC) | ADC_PRESCALER;
\r
391 do { // Wait for conversion to finish.
\r
392 } while (!(ADCSRA & (1<<ADIF)));
\r
394 ADCSRA |= (1<<ADIF); // Clear ADC interrupt flag manually.
\r
396 ADCS.ADC3_G20_OS = ADC; // Save the sampled offset.
\r
398 ADMUX = (1<<REFS0) | 0x16; // ADC5/ADC5 (external ref.), 20x
\r
400 // Start conversion, no interrupt. ADC_PRESCALER is defined in ADC.h.
\r
401 ADCSRA = (1<<ADEN) | (1<<ADSC) | ADC_PRESCALER;
\r
403 do { // Wait for conversion to finish.
\r
404 } while (!(ADCSRA & (1<<ADIF)));
\r
406 ADCSRA |= (1<<ADIF); // Clear ADC interrupt flag.
\r
408 ADCS.ADC5_G20_OS = ADC; // Save the sampled offset.
\r
410 // Reset the ADC-cycle.
\r
413 ADMUX = (1<<REFS0) | ADCS.MUX;
\r
415 // Clear averaged battery current and the discrete readings.
\r
418 for (i = 0; i < 4; i++) {
\r
419 ADCS.discIBAT[i] = 0;
\r
422 // Re-enable the ADC and ISR.
\r
423 ADCSRA=(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|ADC_PRESCALER;
\r
425 __enable_interrupt();
\r
427 // Get a complete cycle of data before returning.
\r