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