e34994f7d704e268184cd4910af2e0d2dbd9fc16
[avr_bc100.git] / BaseTinyFirmware / IAR / statefunc.c
1 /* This file has been prepared for Doxygen automatic documentation generation.*/\r
2 /*! \file *********************************************************************\r
3  *\r
4  * \brief\r
5  *      State functions\r
6  *\r
7  *      Contains the functions related to the states defined in menu.h.\n\r
8  *      Also contains related functions, i.e. for checking jumpers, setting\r
9  *      error flags and "dozing".\r
10  *\r
11  *      \note The state function Charge() is in a separate file since it\r
12  *      should easily be changed with regard to battery type.\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\r
25  *\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/statefunc.c $\r
31  * $Date: 2007-08-23 12:55:51 +0200 (to, 23 aug 2007) $\n\r
32  ******************************************************************************/\r
33 \r
34 #include <ioavr.h>\r
35 #include <inavr.h>\r
36 #include <stdlib.h>\r
37 \r
38 #include "structs.h"\r
39 #include "enums.h"\r
40 \r
41 #include "ADC.h"\r
42 #include "statefunc.h"\r
43 #include "battery.h"\r
44 #include "charge.h"\r
45 #include "main.h"\r
46 #include "menu.h"\r
47 #include "OWI.h"\r
48 #include "PWM.h"\r
49 #include "time.h"\r
50 #include "USI.h"\r
51 \r
52 \r
53 //******************************************************************************\r
54 // Variables\r
55 //******************************************************************************\r
56 unsigned char ErrorFlags;  //!< \brief Holds error flags.\r
57                            //!< \note See statefunc.h for definitions of flags.\r
58 \r
59 //! \brief Holds the state in which latest error flag was set.\r
60 //! \note See menu.h for definitions of states.\r
61 unsigned char ErrorState;\r
62 \r
63 \r
64 //******************************************************************************\r
65 // Functions\r
66 //******************************************************************************\r
67 /*! \brief Initialization\r
68  *\r
69  * - Sets the system clock prescaler to 1 (run at 8 MHz)\r
70  * - Initializes the one-wire interface\r
71  * - Clears on-chip EEPROM\r
72  * - Sets battery enable pins as outputs, then disables batteries\r
73  * - Initializes SPI according to \ref SPIMODE\r
74  * - Initializes ADC\r
75  * - Initializes timers\r
76  * - Reads battery data from both battery inputs (via ADC)\r
77  * - Disables batteries again\r
78  * - Sets battery A as the current one (\ref BattActive = 0)\r
79  * - Clears ErrorFlags\r
80  *\r
81  * \param inp Not used.\r
82  *\r
83  * \retval ST_BATCON Next state in the sequence.\r
84  */\r
85 unsigned char Initialize(unsigned char inp)\r
86 {\r
87         unsigned char i, page;\r
88 \r
89         // Disable interrupts while setting prescaler.\r
90         __disable_interrupt();\r
91         \r
92         CLKPR = (1<<CLKPCE);          // Enable CLKPS bit modification.\r
93         CLKPR = 0;                    // Set prescaler 1 => 8 MHz clock frequency.\r
94         \r
95         // Init 1-Wire(R) interface.\r
96         OWI_Init(OWIBUS);\r
97         \r
98         // Clear on-chip EEPROM.\r
99         for (page = 0; page < 4; page++)        {\r
100                 for (i = 0; i < 32; i++) {\r
101                         BattEEPROM[page][i] = 0;\r
102                 }\r
103         }\r
104 \r
105         DDRB = (1<<PB4) | (1<<PB5);   // Set battery enable pins as outputs.\r
106         DisableBatteries();\r
107         SPI_Init(SPIMODE);\r
108         ADC_Init();\r
109         Time_Init();\r
110 \r
111         // Attempt to get ADC-readings (also gets RID-data) from both batteries.\r
112         for (i = 0; i < 2; i++) {\r
113                 EnableBattery(i);\r
114                 ADC_Wait();\r
115                 BatteryStatusRefresh();\r
116         }\r
117     \r
118         DisableBatteries();\r
119         \r
120         BattActive = 0;               // We have to start somewhere..\r
121         ErrorFlags = 0;\r
122         \r
123         // Init complete! Go to ST_BATCON next.\r
124         return(ST_BATCON);\r
125 }\r
126 \r
127 \r
128 /*! \brief Tests jumper settings and batteries, starts charging if necessary.\r
129  *\r
130  * First, JumperCheck() is called. If successful, the function checks if any\r
131  * valid batteries are connected and attempts to charge these, if necessary.\n\r
132  * If no charging is necessary, the charger goes to ST_SLEEP next.\n\r
133  * ST_ERROR is next if either JumperCheck() fails or there are no valid\r
134  * batteries. In this last case, the error is also flagged.\r
135  *\r
136  * \param inp Not used.\r
137  *\r
138  * \retval ST_ERROR Next state if either the jumper check failed, or there are\r
139  * no valid batteries.\r
140  * \retval ST_PREQUAL Next state if a battery is found to enabled and not fully\r
141  * charged.\r
142  * \retval ST_SLEEP Next state if battery/batteries are enabled and fully\r
143  * charged.\r
144  */\r
145 unsigned char BatteryControl(unsigned char inp)\r
146 {\r
147         unsigned char i;\r
148         \r
149         // Make sure ADC inputs are configured properly! (Will disables batteries.)\r
150         if (!JumperCheck()) {\r
151                 return(ST_ERROR);           // Error. Exit before damage is done!\r
152         }\r
153         \r
154         // If neither battery is valid, flag error and go to error state\r
155         if ((!BattControl[0].Enabled) && (!BattControl[1].Enabled)) {\r
156                 SetErrorFlag(ERR_NO_BATTERIES_ENABLED);\r
157                 \r
158                 return(ST_ERROR);\r
159         }\r
160 \r
161         // Get ADC-readings, try to read EPROM, and start prequalification\r
162         // of any uncharged battery.\r
163         for (i = 0; i < 2; i++) {\r
164                 if (BattControl[i].Enabled) {      \r
165                         EnableBattery(i);\r
166                         ADC_Wait();\r
167 \r
168                         if (BatteryStatusRefresh()) {\r
169                                 if (!BattData.Charged) {\r
170                                         BatteryDataRefresh();\r
171 \r
172                                         return(ST_PREQUAL);       \r
173                                 }\r
174                         }\r
175                 }\r
176         }\r
177 \r
178         // If we end up here, one or two batteries are found and fully charged.\r
179         // Disconnect, so we don't drain them, and go to sleep.\r
180         DisableBatteries();\r
181 \r
182         return(ST_SLEEP);\r
183 }\r
184 \r
185 \r
186 /*! \brief Start running on batteries\r
187  *\r
188  * \todo Run on batteries, if battery voltage high enough.\r
189  * \todo Jump here when mains voltage drops below threshold\r
190  *\r
191  */\r
192 unsigned char Discharge(unsigned char inp)\r
193 {\r
194         return(ST_BATCON);  // Supply voltage restored, start charging\r
195 }\r
196 \r
197 \r
198 /*! \brief Sleeps until either battery needs charging\r
199  *\r
200  * Calls Doze(), then refreshes the status for both batteries on wakeup. If\r
201  * connected batteries are both charged, the function will loop. If not, it's\r
202  * back to ST_BATCON.\r
203  *\r
204  * \param inp Not used.\r
205  *\r
206  * \retval ST_BATCON Next state if a connected battery isn't fully charged.\r
207  */\r
208 unsigned char Sleep(unsigned char inp)\r
209 {\r
210         unsigned char i;\r
211 \r
212         do {\r
213                 Doze();               // Take a nap (~8 seconds).\r
214 \r
215                 // If any batteries need charging, go to ST_BATCON.\r
216                 // Otherwise, keep sleeping.\r
217                 for (i = 0; i < 2; i++) {\r
218                         EnableBattery(i);\r
219                         ADC_Wait();\r
220                         if ((BatteryStatusRefresh()) && (!BattData.Charged)) {\r
221                                 return(ST_BATCON);\r
222                         }\r
223                 }\r
224                 \r
225                 DisableBatteries();  // Disable both batteries before Doze()!\r
226         } while (TRUE);\r
227 }\r
228 \r
229 \r
230 /*! \brief Doze off for approx. 8 seconds (Vcc = 5 V)\r
231  *\r
232  * Waits for ADC-cycles to complete, disables the ADC, then sleeps for\r
233  * approx. 8 seconds (Vcc = 5 V) using the watchdog timer.\r
234  * On wakeup, ADC is re-enabled.\r
235  */\r
236 void Doze(void)\r
237 {\r
238         // Wait for this ADC cycle to complete, then halt after the next one.\r
239         ADC_Wait();\r
240         ADCS.Halt = TRUE;\r
241         ADCS.Flag = FALSE;\r
242         \r
243         do {\r
244         } while (ADCS.Flag == FALSE);    \r
245         \r
246         WDTCR = (1<<WDP3)|(1<<WDP0);            // 8.0 seconds at 5 volts VCC.\r
247         WDTCR |= (1<<WDIF)|(1<<WDIE)|(1<<WDE);  // Clear flag and enable watchdog.\r
248         MCUCR |= (1<<SE) | (1<<SM1)|(1<<SM0);   // Sleep enable, mode = standby.\r
249         __sleep();                              // Go to sleep, wake up by WDT.\r
250         \r
251         __watchdog_reset();                     // Clear watchdog reset flag.\r
252         MCUSR &= ~(1<<WDRF);          \r
253         WDTCR |= (1<<WDCE)|(1<<WDE);            // Watchdog change enable.\r
254         WDTCR = 0;                              // Turn off watchdog.\r
255         \r
256         ADCS.Halt = FALSE;                      // Enable consecutive runs of ADC.\r
257         ADCSRA |= (1<<ADEN)|(1<<ADSC);          // Enable ADC & start conversion.\r
258         \r
259         // Wait for this cycle to complete.\r
260         ADC_Wait();                             \r
261 }\r
262 \r
263 \r
264 /*! \brief Handles errors\r
265  *\r
266  * Stops PWM output and disables batteries. The function then goes into a loop\r
267  * that starts with a call to Doze(), then attempts to handle each error. The\r
268  * loop will reiterate until all flags are cleared.\n\r
269  * The charger will reinitialize after this.\r
270  *\r
271  * Jumper errors are handled by clearing the flag, then calling JumperCheck().\r
272  * If unsuccessful, the error flag will now have been set again.\n\r
273  *\r
274  * If there are no valid batteries, the loop will simply reiterate until a\r
275  * valid battery is found. The error flag will then be cleared.\n\r
276  *\r
277  * In the case of PWM controller or battery temperature errors, the error\r
278  * flag is simply cleared. This is because the problem may have gone away during\r
279  * Doze(), or after reinitializing.\n\r
280  *\r
281  * If a battery is exhausted, we clear its exhausted-flag in \ref BattData,\r
282  * and change batteries before clearing the error flag.\r
283  *\r
284  * \param inp Not used.\r
285  */\r
286 unsigned char Error(unsigned char inp)\r
287         {\r
288         unsigned char i;\r
289         \r
290         PWM_Stop();           // Stop charging.\r
291         DisableBatteries();   // Disable all loads.\r
292         \r
293         do {\r
294                 Doze();           // Take a nap.\r
295 \r
296                 // For each bit in ErrorFlags, starting with LSB, handle\r
297                 // associated error, if the flag is set.\r
298                 for (i = 0x01; i!=0; i<<=1) {\r
299                         if(i & ErrorFlags) {\r
300                                 switch (i) {\r
301                                 \r
302                                 case  ERR_JUMPER_MISMATCH:\r
303                                         // Clear flag & recheck.\r
304                                         ErrorFlags &= ~i;\r
305                                         JumperCheck();\r
306                                 break;\r
307 \r
308 \r
309                                 case  ERR_NO_BATTERIES_ENABLED:\r
310                                         // Clear if any battery gets enabled.\r
311                                         if ((BattControl[0].Enabled) || (BattControl[1].Enabled)) {\r
312                                                         ErrorFlags &= ~i;\r
313                                         }\r
314                                 break;\r
315 \r
316 \r
317                                 case  ERR_PWM_CONTROL:\r
318                                         // Clear flag.\r
319                                         ErrorFlags &= ~i;\r
320                                 break;\r
321 \r
322 \r
323                                 case  ERR_BATTERY_TEMPERATURE:\r
324                                         // Clear flag.\r
325                                         ErrorFlags &= ~i;\r
326                                 break;\r
327 \r
328 \r
329                                 case  ERR_BATTERY_EXHAUSTED:\r
330                                         // Try the other battery.\r
331                                         BattData.Exhausted = FALSE;\r
332                                         BattActive = (BattActive + 1) % 2;\r
333                                         ErrorFlags &= ~i;\r
334                                 break;\r
335 \r
336                                         \r
337                                 default:\r
338                                 break;\r
339                                 }\r
340                         }\r
341                 }\r
342         } while (ErrorFlags);\r
343 \r
344         return(ST_INIT);\r
345 }\r
346 \r
347 \r
348 /*! \brief Sets the specified error-flag and saves the current state\r
349  *\r
350  * Updates \ref ErrorFlags and \ref ErrorState.\r
351  *\r
352  * \note Error flags are specified in statefunc.h.\r
353  *\r
354  * \param Flag Specifies what error to flag.\r
355  */\r
356 void SetErrorFlag(unsigned char Flag)\r
357 {\r
358         ErrorFlags |= Flag;\r
359         ErrorState = CurrentState;\r
360 }\r
361 \r
362 \r
363 /*! \brief Checks on-board jumpers.\r
364  *\r
365  * Checks on-board jumpers by disconnecting all loads, engaging the PWM and\r
366  * increasing the duty cycle until conditioned output voltage equals conditioned\r
367  * input voltage. At low PWM duty and no load buck output should be zero and,\r
368  * when increasing PWM duty, should quickly jump to steady state output roughly\r
369  * equal to input voltage. Will disable and leave disabled all batteries.\r
370  *\r
371  * \retval FALSE If jumper or load mismatch.\r
372  * \retval TRUE If everything OK.\r
373  */\r
374 unsigned char JumperCheck(void)\r
375 {\r
376          DisableBatteries();       // Disconnect, or loads may be destroyed!\r
377          \r
378          PWM_Start();              // Start PWM (controls the buck charger).\r
379         \r
380          // Use general timer: shouldn't take longer than (6 x 255) / 2500 ~= 0.62s.\r
381          Time_Set(TIMER_GEN,0,1,0);\r
382         \r
383         do {\r
384                 // If the PWM output voltage saturates the ADC, stop PWM output and\r
385                 // report a failure.\r
386                 if (ADCS.rawVBAT == 1023) {\r
387                         PWM_Stop();\r
388                         return(FALSE);\r
389                 }\r
390 \r
391                 // If the absolute difference between measured (VIN - VBAT) and the\r
392                 // typical value are below our set maximum, everything is OK.\r
393                 if (abs((signed int)(ADCS.VIN - VIN_VBAT_DIFF_TYP - ADCS.VBAT)) <\r
394                                      VIN_VBAT_DIFF_MAX ) {\r
395                                  \r
396                         PWM_Stop();\r
397                         return(TRUE);\r
398                 }\r
399 \r
400                 // Charge current is too high -> check load and jumper J405 and J406.\r
401                 if (abs(ADCS.IBAT) > 100) {\r
402                         PWM_Stop();\r
403                         return(FALSE);\r
404                 }\r
405 \r
406                 // If the PWM output can't be increased high enough -> check jumpers\r
407                 // J400-J404, J407 and J408.\r
408                 if (!PWM_IncrementDutyCycle()) {\r
409                         PWM_Stop();\r
410                         return(FALSE);\r
411                 }\r
412                 \r
413       // Wait for ADC conversions to complete\r
414                 ADC_Wait();\r
415         } while (Time_Left(TIMER_GEN));\r
416         \r
417 \r
418         // If we end up here, the measurements took too long.\r
419         PWM_Stop();\r
420         return(FALSE);\r
421 }\r