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