Initial import
[avr_bc100.git] / BaseTinyFirmware / GCC / battery.c
1 /* This file has been prepared for Doxygen automatic documentation generation.*/\r
2 /*! \file *********************************************************************\r
3  *\r
4  * \brief\r
5  *      Functions related to battery control and data acquisition.\r
6  *\r
7  *      Contains functions for enabling/disabling batteries, and looking up\r
8  *      their status and specifications using the ADC.\r
9  *\r
10  * \par Application note:\r
11  *      AVR458: Charging Li-Ion Batteries with BC100 \n\r
12  *      AVR463: Charging NiMH Batteries with BC100\r
13 \r
14  *\r
15  * \par Documentation\r
16  *      For comprehensive code documentation, supported compilers, compiler \r
17  *      settings and supported devices see readme.html\r
18  *\r
19  * \author\r
20  *      Atmel Corporation: http://www.atmel.com \n\r
21  *      Support email: avr@atmel.com\r
22  *\r
23  * \r
24  * $Name$\r
25  * $Revision: 2299 $\r
26  * $RCSfile$\r
27  * $URL: http://svn.norway.atmel.com/AppsAVR8/avr458_Charging_Li-Ion_Batteries_with_BC100/tag/20070904_release_1.0/code/IAR/battery.c $\r
28  * $Date: 2007-08-23 12:55:51 +0200 (to, 23 aug 2007) $\n\r
29  ******************************************************************************/\r
30 \r
31 #include <avr/io.h>\r
32 #include <avr/eeprom.h>\r
33 \r
34 #include "structs.h"\r
35 #include "enums.h"\r
36 \r
37 #include "ADC.h"\r
38 #include "battery.h"\r
39 #include "main.h"\r
40 #include "OWI.h"\r
41 #include "time.h"\r
42 \r
43 #ifdef NIMH\r
44 #include "NIMHspecs.h"\r
45 #endif // NIMH\r
46 \r
47 #ifdef LIION\r
48 #include "LIIONspecs.h"\r
49 #endif // LIION\r
50 \r
51 \r
52 \r
53 //******************************************************************************\r
54 // Variables\r
55 //******************************************************************************\r
56 /* Control-struct for batteries */\r
57 /*! \brief Holds control data for both batteries\r
58  * \note Stored in EEPROM.\r
59  */\r
60 \r
61 Battery_t EEMEM BattControl[2] = {(BIT_BATTERY_ENABLED | BIT_BATTERY_DISCONNECT_ALLOWED | BIT_BATTERY_CHARGE_INHIBIT),\r
62                                   (BIT_BATTERY_ENABLED | BIT_BATTERY_DISCONNECT_ALLOWED | BIT_BATTERY_CHARGE_INHIBIT),\r
63 };\r
64 \r
65 /* Data-struct for battery */\r
66 Batteries_t BattData; //!< Holds data for the current battery\r
67 \r
68 \r
69 /* Storage for battery EPROM */\r
70 /*! \brief Storage space for data from the batteries' own EPROMs.\r
71  * \note Stored in EEPROM.\r
72  */\r
73 unsigned char EEMEM BattEEPROM[4][32];\r
74 \r
75 \r
76 //! Global that indicates current battery (0 = battery A, 1 = B)\r
77 unsigned char BattActive;\r
78 \r
79 \r
80 /*! \brief RID lookup-table\r
81  *\r
82  * Contains Resistor ID data specified by manufacturer.\r
83  *\r
84  * \note Values have been calculated assuming a +/- 15% tolerance.\r
85  */\r
86 const RID_Lookup_t RID[RID_TABLE_SIZE] = {\r
87   {558, 659, 3900, 550, 260, 300, 10},\r
88   {744, 843, 6800, 750, 360, 300, 14},\r
89   {869, 958, 10000, 1000, 475, 300, 19},\r
90   {1097, 1153, 24000, 2000, 475, 420, 38}\r
91 };\r
92 \r
93 \r
94 /*! \brief NTC lookup-table\r
95  *\r
96  * The first entry is 0 degrees. For every entry after, temperature increases\r
97  * with 4 degrees. With 20 entries, the last one equals (20-1)*4 = 76 degrees.\n\r
98  * It must be sorted in descending ADC order.\r
99  *\r
100  * \note This was calculated for a Mitsubishi RH16-3H103FB NTC.\r
101  *\r
102  * \note NTCLookUp() must be modified if this table is changed so that:\r
103  * - first entry is no longer 0 degrees.\r
104  * - temperature difference between entries is no longer 4 degrees.\r
105  * - ADCsteps no longer specifies ADC steps per half degree.\r
106  */\r
107 // FOR VARTA POLYFLEX NTC\r
108 const NTC_Lookup_t NTC[NTC_TABLE_SIZE] = {\r
109         {1002, 23}, {953, 25}, {902, 26}, {849, 27}, {796, 27},\r
110         {742, 27}, {689, 26}, {637, 26}, {587, 25}, {539, 24},\r
111         {494, 22}, {451, 21}, {412, 19}, {375, 18}, {341, 17},\r
112         {310, 15}, {282, 14}, {256, 13}, {233, 11}, {212, 10}\r
113 };\r
114 \r
115 \r
116 // FOR MITSUBISHI NTC\r
117 /*\r
118 const NTC_Lookup_t NTC[NTC_TABLE_SIZE] = {\r
119   {1004, 24}, {954, 25}, {903, 26}, {850, 27}, {796, 27},\r
120   {742, 27}, {689, 27}, {637, 26}, {587, 25}, {539, 24},\r
121   {493, 22}, {451, 21}, {411, 20}, {374, 18}, {340, 17},\r
122   {309, 15}, {281, 14}, {255, 13}, {232, 11}, {211, 10}\r
123 };\r
124 */\r
125 \r
126  \r
127 //******************************************************************************\r
128 // Functions\r
129 //******************************************************************************\r
130 /*! \brief Checks if battery has changed\r
131  *\r
132  * Stores current capacity, then attempts to refresh battery status.\n\r
133  * If the refresh is successful, old capacity is compared with the new one.\r
134  * \r
135  * \retval FALSE Battery is disconnected, or capacity has changed.\r
136  * \retval TRUE All OK.\r
137  */\r
138 unsigned char BatteryCheck(void)\r
139 {\r
140         unsigned char success = TRUE;\r
141         unsigned int  oldCapacity;\r
142         \r
143         // Save to see if battery data has changed.\r
144         oldCapacity = BattData.Capacity;  \r
145         \r
146         if (!BatteryStatusRefresh()) {\r
147                 success = FALSE;              // Battery not present or RID was invalid.\r
148         }\r
149         \r
150         if (oldCapacity != BattData.Capacity) {\r
151                 success = FALSE;              // Battery configuration has changed.\r
152         }\r
153 \r
154         return(success);\r
155 }\r
156 \r
157 \r
158 /*! \brief Refreshes battery status information\r
159  *\r
160  * Refreshes battery status information, if it is present, based on\r
161  * RID and NTC (read by ADC).\n\r
162  * The battery must have been enabled and a complete set of ADC data must have\r
163  * been collected before calling.\n\r
164  *\r
165  * \retval FALSE No battery present.\r
166  * \retval TRUE Battery present, status refreshed.\r
167  *\r
168  * \note If ALLOW_NO_RID is defined, charging will NOT stop if no fitting entry\r
169  * is found in the lookup table. Instead, default battery data will be used.\r
170  */\r
171 unsigned char BatteryStatusRefresh(void)\r
172 {\r
173         // Assume the worst..\r
174         unsigned char success = FALSE;\r
175         \r
176         BattData.Present = FALSE;\r
177         BattData.Charged = FALSE;\r
178         BattData.Low = TRUE;\r
179         BattData.Circuit = OW_NONE;\r
180         BattData.Temperature = 0;\r
181         BattData.Capacity = 0;\r
182         BattData.MaxCurrent = 0;\r
183         BattData.MaxTime = 0;\r
184         BattData.MinCurrent = 0;\r
185 \r
186         NTCLookUp();\r
187         BattData.HasRID = RIDLookUp();\r
188 \r
189         // Is the battery voltage above minimum safe cell voltage?\r
190         if (ADCS.VBAT >= BAT_VOLTAGE_MIN) {\r
191                 BattData.Low = FALSE;\r
192         }\r
193 \r
194         // Is the battery charged?\r
195         if (ADCS.VBAT >= BAT_VOLTAGE_LOW) {\r
196                 BattData.Charged = TRUE;\r
197         }\r
198 \r
199         // If we are not charging, yet VBAT is above safe limit, battery is present.\r
200         // If we are charging and there's a current flowing, the battery is present.\r
201         \r
202         /*! \todo If ABORT_IF_PWM_MAX is defined this last check battery presence\r
203         * check is redundant since charging will be aborted due to low current at\r
204         * max duty cycle. That is preferrable since the charge current reading is\r
205         * not 100% proof.\r
206         */\r
207         if (((OCR1B == 0) && (!BattData.Low)) ||\r
208             ((OCR1B != 0) && (ADCS.avgIBAT > 0))) {\r
209                 BattData.Present = TRUE;\r
210                 success = TRUE;\r
211         } else {\r
212                 BattData.Low = FALSE;  // (This is just a technicality..)\r
213                 success = FALSE;\r
214         }\r
215 \r
216 #ifndef ALLOW_NO_RID\r
217         // Return FALSE if no valid RID entry was found, to stop charging.\r
218         if(!BattData.HasRID) {\r
219                 success = FALSE;\r
220         }\r
221 #endif\r
222 \r
223         return(success);\r
224 }\r
225 \r
226 \r
227 /*! \brief Refreshes battery data in the EEPROM\r
228  *\r
229  * Attempts to read 4 pages of 32 bytes each from the battery's EPROM and store\r
230  * these data in on-chip EEPROM.\n\r
231  * If unsuccessful (CRC doesn't check out), the on-chip EEPROM is cleared.\r
232  *\r
233  * \todo Updating BattData with these data. Specs needed.\r
234  *\r
235  * \retval FALSE Refresh failed.\r
236  * \retval TRUE Refresh successful.\r
237  */\r
238 unsigned char BatteryDataRefresh(void)\r
239 {\r
240         unsigned char offset;\r
241         unsigned char i, crc, family, temp, page;\r
242         unsigned char success;\r
243         \r
244         // Look for EPROM and read 4 pages of 32 bytes each worth of data, if found.\r
245         for (page = 0; page < 4; page++)        {\r
246                 success = FALSE;\r
247         \r
248                 if (OWI_DetectPresence(OWIBUS) == OWIBUS) {\r
249                         \r
250                         // Presence detected, check type and CRC.\r
251                         OWI_SendByte(OWI_ROM_READ, OWIBUS);\r
252                         family = OWI_ReceiveByte(OWIBUS);\r
253                         crc = OWI_ComputeCRC8(family,0);\r
254 \r
255                         for (i = 0; i < 6; i++) {\r
256                                 crc = OWI_ComputeCRC8(OWI_ReceiveByte(OWIBUS),crc);\r
257                         }\r
258 \r
259                         // CRC ok, device found.\r
260                         if (OWI_ComputeCRC8(OWI_ReceiveByte(OWIBUS),crc) == 0) {\r
261                                 BattData.Circuit = family;\r
262 \r
263                                 // For now, we only read data from DS2505 EPROMs.\r
264                                 if (BattData.Circuit == OW_DS2505) {\r
265                                         offset = page*32;\r
266                                         OWI_SendByte(DS2505_DATA_READ, OWIBUS);  // Command: read data.\r
267                                         OWI_SendByte(offset, OWIBUS);            // Data: low address.\r
268                                         OWI_SendByte(0, OWIBUS);                 // Data: high address.\r
269 \r
270                                         // Calculate checksums.\r
271                                         crc = OWI_ComputeCRC8(DS2505_DATA_READ,0);\r
272                                         crc = OWI_ComputeCRC8(offset,crc);\r
273                                         crc = OWI_ComputeCRC8(0,crc);\r
274 \r
275                                         // Command received succesfully, now start reading data\r
276                                         // and writing it to EEPROM.\r
277                                         if (OWI_ComputeCRC8(OWI_ReceiveByte(OWIBUS),crc) == 0) {\r
278                                                 crc = 0;\r
279                                                 \r
280                                                 // Fill page with data.\r
281                                                 for (i=0; i<32; i++) {\r
282                                                         temp = OWI_ReceiveByte(OWIBUS);\r
283                                                         crc = OWI_ComputeCRC8(temp, crc);\r
284                                                         eeprom_write_byte(&BattEEPROM[page][i], temp);\r
285                                                 }\r
286                                                 \r
287                                                 if (OWI_ComputeCRC8(OWI_ReceiveByte(OWIBUS),crc) == 0) {\r
288                                                                 success = TRUE;  // Data read OK\r
289                                                 }\r
290                                         } else { // Not able to start reading data.\r
291                                         }\r
292                                 } else { // Wrong device type.\r
293                                 }\r
294                         } else { // No device found.             \r
295                         }\r
296                 } else { // No presence detected on one-wire bus.\r
297                 }\r
298 \r
299           // Erase local EEPROM page if there were any errors during transfer.\r
300                 if (!success) {\r
301                         for (i=0; i<32; i++) {\r
302                           eeprom_write_byte(&BattEEPROM[page][i], 0);\r
303                         }\r
304                 }\r
305         }\r
306 \r
307         return(success);\r
308 }\r
309 \r
310 \r
311 /*! \brief Enables specified battery\r
312  *\r
313  * Updates \ref BattActive to specified battery, then sets PB4/PB5 and clears\r
314  * PB5/PB4 in PORTB, depending on which battery is specified.\n\r
315  * The function takes 100 ms to allow the port switch to settle.\r
316  *\r
317  * \param bat Specifies which battery to enable (0 = battery A, 1 = B)\r
318  */\r
319 void EnableBattery(unsigned char bat)\r
320 {\r
321         // Use general timer, set timeout to 100ms.\r
322         Time_Set(TIMER_GEN,0,0,100);\r
323 \r
324         // Set specified battery as the active one.\r
325         BattActive = bat;\r
326 \r
327         // Enable current battery in hardware, light LED & connect battery.\r
328         PORTB |= (1 << (PB4+bat));\r
329 \r
330         // Disconnect other battery.\r
331         PORTB &= ~(1<<(PB5-bat));     \r
332 \r
333         do { // Let port switch settle.\r
334         } while (Time_Left(TIMER_GEN));  \r
335 }\r
336 \r
337 \r
338 /*! \brief Disables both batteries\r
339  *\r
340  * Clears PB4 and PB5 in PORTB, disabling both batteries.\r
341  */\r
342 void DisableBatteries(void)\r
343 {\r
344         // Turn off LEDs and disconnect batteries.\r
345         PORTB &= ~((1<<PB4)|(1<<PB5));  \r
346 }\r
347 \r
348 \r
349 /*! \brief Looks up battery data from RID table\r
350  *\r
351  * Attempts to find data for the battery from the RID lookup-table.\n\r
352  * If no valid entry is found, default data (defined in battery.h)\r
353  * are used.\r
354  *\r
355  * \retval TRUE Entry found, battery data updated.\r
356  * \retval FALSE No entry found, using defaults for battery data.\r
357  */\r
358 unsigned char RIDLookUp (void)\r
359 {\r
360         unsigned char i, found = FALSE;\r
361                 \r
362         // Lookup in the RID-table. If measured RID is within the limits\r
363         // of an entry, those data are used, and TRUE is returned.\r
364         for (i = 0 ; i < RID_TABLE_SIZE; i++) {\r
365                 if (ADCS.rawRID >= RID[i].Low) {\r
366                         if (ADCS.rawRID <= RID[i].High) {\r
367                                 BattData.Capacity = RID[i].Capacity;\r
368                                 BattData.MaxCurrent = RID[i].Icharge;\r
369                                 BattData.MaxTime = RID[i].tCutOff;\r
370                                 BattData.MinCurrent = RID[i].ICutOff;\r
371                                 \r
372                                 found = TRUE;\r
373                         }\r
374                 }\r
375         }\r
376         \r
377         // If no valid entry is found, use defaults and return FALSE.\r
378         if (!found) {\r
379                 BattData.Capacity = DEF_BAT_CAPACITY;\r
380                 BattData.MaxCurrent = DEF_BAT_CURRENT_MAX;\r
381                 BattData.MaxTime = DEF_BAT_TIME_MAX;\r
382                 BattData.MinCurrent = DEF_BAT_CURRENT_MIN;\r
383         }\r
384         \r
385         return(found);\r
386 }\r
387 \r
388 \r
389 /*! \brief Calculates temperature from a lookup table\r
390  *\r
391  * Looks up the highest NTC value below or equal to the measured one.\n\r
392  * With the current lookup table, temperature is calculated with the formula:\n\r
393  * 4*(index of entry) - 2*(measured NTC - NTC from entry) / (ADCsteps of entry)\r
394  *\r
395  * \note If the NTC-measurement is saturated, with the current lookup table,\r
396  * the temperature will be reported as -1 C.\r
397  *\r
398  * \note If no valid entry is found, battery temperature is set to 80.\r
399  */\r
400 void NTCLookUp (void)\r
401 {\r
402         unsigned char i;\r
403         unsigned char found = FALSE;\r
404         \r
405         // Lookup in the NTC-table. Use the first entry which is equal or below\r
406         // sampled NTC. Calculate temperature by using the index number, and the\r
407         // difference between the measured NTC value and the one in the entry.\r
408         for (i=0 ; (i < NTC_TABLE_SIZE) && (!found); i++)       {\r
409                 if (ADCS.rawNTC >= NTC[i].ADCV) {\r
410                         BattData.Temperature = (i<<2) ;\r
411                         BattData.ADCSteps = NTC[i].ADCsteps;  \r
412                         BattData.Temperature -= ((ADCS.rawNTC - NTC[i].ADCV)<<1) / BattData.ADCSteps;\r
413                         \r
414                         found = TRUE;  // Could be done with a break, but that violates MISRA.\r
415                 }\r
416         }\r
417         \r
418         // For safety, is temperature is greater than the NTC \r
419         if (!found) {\r
420                 BattData.Temperature = 80;\r
421         }\r
422 }\r