sd-card: Only add header to the results file if it’s empty
[brewing-logger:firmware.git] / sleep.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * Brewing Logger.
4  * Copyright (C) Philip Withnall 2012 <philip@tecnocode.co.uk>
5  *
6  * Brewing Logger is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Brewing Logger is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Brewing Logger.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include "config.h"
21
22 #include <avr/interrupt.h>
23 #include <avr/io.h>
24 #include <avr/sleep.h>
25 #include <avr/wdt.h>
26
27 #include "button.h"
28 #include "common.h"
29 #include "sleep.h"
30
31 /**
32  * \file
33  * \brief Sleep control.
34  *
35  * Implementation of sleep states for the microcontroller, and their interaction with event handling. This allows the microcontroller to be put into
36  * a deep sleep state for multiples of 8 seconds, being woken up by a timer or an external interrupt (such as the user pressing the UI button).
37  */
38
39 /* Initialise the watchdog to generate a timer interrupt every 8s. */
40 static void _watchdog_init (void)
41 {
42         cli (); /* disable interrupts */
43         wdt_reset (); /* reset the watchdog */
44
45         /* Start timed sequence */
46         WDTCSR |= (1 << WDCE) | (1 << WDE);
47
48         /* Set new prescaler (time-out) value = 1024K cycles (~8s); interrupts enabled */
49         WDTCSR = (1 << WDIE) | (1 << WDP3) | (1 << WDP0);
50
51         sei (); /* re-enable interrupts */
52 }
53
54 ISR (WDT_vect)
55 {
56         /* Watchdog vector. Nothing to see here. */
57         wdt_reset ();
58 }
59
60 void sleep_init (void)
61 {
62         _watchdog_init ();
63 }
64
65 /* Sleep for ~8s (or until the UI button is pressed) in power-down mode. Return an identifier for the event which caused the wakeup.
66  * This takes care of handling race conditions between interrupts (e.g. button presses) and going to sleep. It also disables the brown-out detector
67  * before sleeping. See: http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html */
68 static WakeupReason _sleep_8s (void)
69 {
70         set_sleep_mode ((1 << SM1)); /* sleep in power-down mode */
71
72         cli ();
73         if (button_was_pressed ()) {
74                 /* The button was pressed. Stay awake and return. */
75                 sei ();
76                 return WAKEUP_BUTTON;
77         }
78
79         /* Reset the watchdog timer before going to sleep. This ensures our sleep period is actually something approaching 8s. */
80         wdt_reset ();
81
82         /* No button press. Atomically go to sleep and re-enable interrupts. This does admit the possibility of a button press waking us up,
83          * some processing being performed, and then _sleep_8s() being called again before the button has stopped bouncing, and thus a
84          * spurious wakeup occurring. We assume the processing will take more than ~10ms, and so ignore the possibility of spurious
85          * wakeups. Consequently, we re-enable the button interrupt before sleeping. */
86         button_reset ();
87         sleep_enable ();
88         sleep_bod_disable ();
89
90         /* Night night. This is effectively atomic because the instruction after an SEI instruction is always guaranteed to execute before
91          * interrupts start to be handled again. */
92         sei ();
93         sleep_cpu ();
94         sleep_disable ();
95
96         /* Check whether we woke up from a button press before returning. */
97         if (button_was_pressed ()) {
98                 return WAKEUP_BUTTON;
99         }
100
101         return WAKEUP_TIMER;
102 }
103
104 /**
105  * \brief Sleep until an event happens.
106  *
107  * Sleep for \c sleep_time seconds, or until the button is pressed. Sleeping is done in power-down mode to reduce power consumption as much as
108  * possible. This handles race conditions between interrupts and putting the CPU to sleep.
109  *
110  * The cause for waking up is returned.
111  */
112 WakeupReason sleep_until_event (uint8_t sleep_time)
113 {
114         uint8_t i;
115
116         if (sleep_time <= 8) {
117                 return _sleep_8s ();
118         }
119
120         /* Note: We only have granularity of 8s at the moment. This is good enough for what we need. */
121         for (i = 0; i < sleep_time / 8; i++) {
122                 if (_sleep_8s () == WAKEUP_BUTTON) {
123                         return WAKEUP_BUTTON;
124                 }
125         }
126
127         return WAKEUP_TIMER;
128 }