aboutsummaryrefslogtreecommitdiff
path: root/spaghetti-monster/fsm-wdt.c
blob: 6e61e87f9e4f44ee14bd16d4d5aff2a6992592de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
/*
 * fsm-wdt.c: WDT (Watch Dog Timer) functions for SpaghettiMonster.
 *
 * Copyright (C) 2017 Selene ToyKeeper
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef FSM_WDT_C
#define FSM_WDT_C

#include <avr/interrupt.h>
#include <avr/wdt.h>

void WDT_on()
{
    #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
        // interrupt every 16ms
        //cli();                          // Disable interrupts
        wdt_reset();                    // Reset the WDT
        WDTCR |= (1<<WDCE) | (1<<WDE);  // Start timed sequence
        WDTCR = (1<<WDIE);              // Enable interrupt every 16ms
        //sei();                          // Enable interrupts
    #elif (ATTINY == 1634)
        wdt_reset();                    // Reset the WDT
        WDTCSR = (1<<WDIE);             // Enable interrupt every 16ms
    #else
        #error Unrecognized MCU type
    #endif
}

#ifdef TICK_DURING_STANDBY
inline void WDT_slow()
{
    #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
        // interrupt slower
        //cli();                          // Disable interrupts
        wdt_reset();                    // Reset the WDT
        WDTCR |= (1<<WDCE) | (1<<WDE);  // Start timed sequence
        WDTCR = (1<<WDIE) | STANDBY_TICK_SPEED; // Enable interrupt every so often
        //sei();                          // Enable interrupts
    #elif (ATTINY == 1634)
        wdt_reset();                    // Reset the WDT
        WDTCSR = (1<<WDIE) | STANDBY_TICK_SPEED;
    #else
        #error Unrecognized MCU type
    #endif
}
#endif

inline void WDT_off()
{
    #if (ATTINY == 25) || (ATTINY == 45) || (ATTINY == 85)
        //cli();                          // Disable interrupts
        wdt_reset();                    // Reset the WDT
        MCUSR &= ~(1<<WDRF);            // Clear Watchdog reset flag
        WDTCR |= (1<<WDCE) | (1<<WDE);  // Start timed sequence
        WDTCR = 0x00;                   // Disable WDT
        //sei();                          // Enable interrupts
    #elif (ATTINY == 1634)
        cli();                // needed because CCP, below
        wdt_reset();          // Reset the WDT
        MCUSR &= ~(1<<WDRF);  // clear watchdog reset flag
        CCP = 0xD8;           // enable config changes
        WDTCSR = 0;           // disable and clear all WDT settings
        sei();
    #else
        #error Unrecognized MCU type
    #endif
}

// clock tick -- this runs every 16ms (62.5 fps)
ISR(WDT_vect) {
    static uint8_t adc_trigger = 0;

    #ifdef TICK_DURING_STANDBY
    f_wdt = 1;  // WDT event happened

    static uint16_t sleep_counter = 0;
    // handle standby mode specially
    if (go_to_standby) {
        // emit a halfsleep tick, and process it
        emit(EV_sleep_tick, sleep_counter);
        // wrap around from 65535 to 32768, not 0
        sleep_counter = (sleep_counter + 1) | (sleep_counter & 0x8000);
        process_emissions();

        #if defined(USE_SLEEP_LVP)
        // stop here, usually...  but proceed often enough for sleep LVP to work
        if (0 != (sleep_counter & 0x7f)) return;
        adc_trigger = 255;  // make sure a measurement will happen
        #else
        return;  // no sleep LVP needed if nothing drains power while off
        #endif
    }
    else { sleep_counter = 0; }
    #endif

    // detect and emit button change events
    uint8_t was_pressed = button_last_state;
    uint8_t pressed = button_is_pressed();
    if (was_pressed != pressed) PCINT_inner(pressed);

    // cache this here to reduce ROM size, because it's volatile
    uint16_t ticks_since_last = ticks_since_last_event;
 
    // increment, but loop from max back to half
    //if (ticks_since_last < 0xff) ticks_since_last ++;
    ticks_since_last = (ticks_since_last + 1) \
                     | (ticks_since_last & 0x8000);
    // copy back to the original
    ticks_since_last_event = ticks_since_last;

    // if time since last event exceeds timeout,
    // append timeout to current event sequence, then
    // send event to current state callback

    // callback on each timer tick
    if ((current_event & B_FLAGS) == (B_CLICK | B_HOLD | B_PRESS)) {
        emit(EV_tick, 0);  // override tick counter while holding button
    }
    else {
        emit(EV_tick, ticks_since_last);
    }

    // user held button long enough to count as a long click?
    if (current_event & B_PRESS) {
        // during a "hold", send a hold event each tick, with a timer
        if (current_event & B_HOLD) {
            emit_current_event(ticks_since_last);
        }
        // has button been down long enough to become a "hold"?
        else {
            if (ticks_since_last >= HOLD_TIMEOUT) {
                //ticks_since_last_event = 0;
                current_event |= B_HOLD;
                emit_current_event(0);
            }
        }
    }

    // event in progress, but button not currently down
    else if (current_event) {
        // "hold" event just ended
        // no timeout required when releasing a long-press
        // TODO? move this logic to PCINT() and simplify things here?
        if (current_event & B_HOLD) {
            //emit_current_event(0);  // should have been emitted by PCINT
            empty_event_sequence();
        }
        // end and clear event after release timeout
        else if (ticks_since_last >= RELEASE_TIMEOUT) {
            current_event |= B_TIMEOUT;
            //ticks_since_last_event = 0;
            emit_current_event(0);
            empty_event_sequence();
        }
    }

    #if defined(USE_LVP) || defined(USE_THERMAL_REGULATION)
    // start a new ADC measurement every 4 ticks
    adc_trigger ++;
    if (0 == (adc_trigger & 3)) {
        #if defined(TICK_DURING_STANDBY) && defined(USE_SLEEP_LVP)
        // we shouldn't be here unless it woke up for a LVP check...
        // so enable ADC voltage measurement functions temporarily
        if (go_to_standby) ADC_on();
        #endif
        ADC_start_measurement();
        adcint_enable = 1;
    }
    #endif
}

#endif