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