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