-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.c
339 lines (294 loc) · 6.89 KB
/
main.c
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
#define F_CPU 128000L
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <stdbool.h>
enum action {
ACT_IDLE,
// direction for these two is determined by switch position
ACT_INNER,
ACT_OUTER,
// "close" or "open" is sw position when rewind began
ACT_REWIND_CLOSE,
ACT_REWIND_OPEN,
ACT_COOLDOWN,
};
enum pins {
PIN_INNER_UP_O,
PIN_INNER_DOWN_O,
PIN_OUTER_UP_O,
PIN_OUTER_DOWN_O,
PIN_SW_OPEN_I,
PIN_SW_CLOSED_I,
PIN_SENS_OPEN_I,
PIN_SENS_CLOSED_I,
};
struct state {
unsigned char action : 3;
unsigned char sw_inner_open : 1;
unsigned char sw_outer_closed : 1;
unsigned char sens_open : 1;
unsigned char sens_closed : 1;
unsigned char inner_done : 1;
};
volatile struct state state = { 0 };
/////////////////////////////////
// pin related functions
static void pin_idle() {
PORTA &= ~(
(1<<PIN_INNER_UP_O) |
(1<<PIN_INNER_DOWN_O) |
(1<<PIN_OUTER_UP_O) |
(1<<PIN_OUTER_DOWN_O));
}
// physically move the outer motor up
static void pin_outer_up() {
pin_idle();
PORTA |= (1<<PIN_OUTER_UP_O);
}
static void pin_outer_down() {
pin_idle();
PORTA |= (1<<PIN_OUTER_DOWN_O);
}
static void pin_inner_up() {
pin_idle();
PORTA |= (1<<PIN_INNER_UP_O);
}
static void pin_inner_down() {
pin_idle();
PORTA |= (1<<PIN_INNER_DOWN_O);
}
// update switch and sensor info in state using pin information.
static void read_pins() {
// avoid races
unsigned char pina = PINA;
bool new_sw_inner_open = !!(pina & (1<<PIN_SW_OPEN_I));
// if inner door changed
if (new_sw_inner_open != state.sw_inner_open) {
state.inner_done = false;
}
state.sw_inner_open = new_sw_inner_open;
state.sw_outer_closed = !!(pina & (1<<PIN_SW_CLOSED_I));
// active low (pulled up)
state.sens_open = !(pina & (1<<PIN_SENS_OPEN_I));
state.sens_closed = !(pina & (1<<PIN_SENS_CLOSED_I));
}
///////////////////////////
// random helpers
static bool outer_done_p() {
return (state.sw_outer_closed && state.sens_closed) ||
(!state.sw_outer_closed && state.sens_open);
}
static void ensure_flshgnd() {
TCCR0B = (1<<CS02);
TCCR0A = (1<<COM0A0);
}
static void clear_flshgnd() {
// stop the counter
TCCR0B = 0;
// the next time flshgnd is enabled, we want it to go black immediately.
if (PINB & (1<<PINB2)) {
TCNT0 = 2;
} else {
TCNT0 = 0;
}
// finally, disconnect the OCR
TCCR0A = 0;
}
////////////////////////////
// state transitions
static void action_idle() {
clear_flshgnd();
state.action = ACT_IDLE;
pin_idle();
// four and a bit minutes
OCR1B = TCNT1 + 0x8000;
}
static void action_inner() {
ensure_flshgnd();
state.action = ACT_INNER;
if (state.sw_inner_open) {
pin_inner_up();
} else {
pin_inner_down();
}
// two seconds
OCR1B = TCNT1 + 0x0100;
}
static void action_outer() {
ensure_flshgnd();
state.action = ACT_OUTER;
if (state.sw_outer_closed) {
pin_outer_down();
} else {
pin_outer_up();
}
// eight seconds, then rewind
OCR1B = TCNT1 + 0x0400;
}
static void auto_action_motion() {
if (state.inner_done) {
if (outer_done_p()) {
action_idle();
} else {
action_outer();
}
} else {
action_inner();
}
}
static void action_rewind_close() {
ensure_flshgnd();
state.action = ACT_REWIND_CLOSE;
// should already be set, but for consistency...
pin_outer_up();
OCR1B = TCNT1 + 0x0800;
}
static void action_rewind_open() {
ensure_flshgnd();
state.action = ACT_REWIND_OPEN;
pin_outer_down();
OCR1B = TCNT1 + 0x0800;
}
static void action_cooldown() {
ensure_flshgnd();
state.action = ACT_COOLDOWN;
pin_idle();
// about a minute
OCR1B = TCNT1 + 0x2000;
}
///////////////////////////////
// act and act-timer
// perform action and update state accordingly.
static void act() {
switch (state.action) {
case ACT_IDLE:
auto_action_motion();
break;
case ACT_INNER:
// no interrupting inner motor, let timer handle it.
break;
case ACT_OUTER:
// avoid interruptions, for good measure.
if (state.sens_open || state.sens_closed) {
auto_action_motion();
}
break;
case ACT_REWIND_CLOSE:
if (state.sens_closed) {
// rewind finished gracefully
auto_action_motion();
} else if (state.sens_open) {
// got stuck on the way down during rewind, so it came
// back up the other (normal spooling) side. Assume a
// serious blockage.
action_cooldown();
}
break;
case ACT_REWIND_OPEN:
if (state.sens_closed || state.sens_open) {
// rewind finished OR the door got caught while
// rewinding (never reached the bottom) and is now open
// but spooled the wrong direction, which will be
// corrected when the door is next opened.
// This could cause a loop of activating the motor in
// case of a serious blockage.
auto_action_motion();
}
break;
case ACT_COOLDOWN:
// do nothing, we're waiting for the timer.
break;
}
}
// perform action, knowing that the timer compare just fired
static void act_timer() {
switch (state.action) {
case ACT_IDLE:
// Inner door periodic
state.inner_done = false;
auto_action_motion();
// special override
clear_flshgnd();
break;
case ACT_INNER:
// it's only done if the direction was not changed while it was
// in motion.
if (!!(PORTA & (1<<PIN_INNER_UP_O)) == state.sw_inner_open) {
state.inner_done = true;
}
auto_action_motion();
break;
case ACT_OUTER:
// Start rewind
if (state.sw_outer_closed) {
action_rewind_close();
} else {
action_rewind_open();
}
break;
case ACT_REWIND_OPEN:
case ACT_REWIND_CLOSE:
// rewind took too long
action_cooldown();
break;
case ACT_COOLDOWN:
// cooldown complete
auto_action_motion();
break;
}
}
/////////////////////////////////
// main and interrupts
int main() {
// Pull high on the door sensors.
PORTA = (1<<PIN_SENS_OPEN_I) | (1<<PIN_SENS_CLOSED_I);
// All port A pins are output. Low defaults are OK: Everything off,
// light solid on.
DDRA =
(1<<PIN_INNER_UP_O) |
(1<<PIN_INNER_DOWN_O) |
(1<<PIN_OUTER_UP_O) |
(1<<PIN_OUTER_DOWN_O);
DDRB = (1<<PINB2); // flshgnd
// 8-bit timer compare
OCR0A = 1;
// enable pin change interrupts
PCMSK0 =
(1<<PIN_SW_OPEN_I) |
(1<<PIN_SW_CLOSED_I) |
(1<<PIN_SENS_OPEN_I) |
(1<<PIN_SENS_CLOSED_I);
GIMSK = (1<<PCIE0);
_delay_ms(5);
// timer 1 runs continuously, used for debouncing and inner door timing.
TCCR1B = (1<<CS12) | (1<<CS10);
// just make sure it doesn't fire immediately. The first act() should
// set it properly (door moves immediately).
OCR1B = 255;
TIMSK1 = (1<<OCIE1B);
read_pins();
act();
sei();
while (true) sleep_mode(); // default sleep is idle, allows timers
// set_sleep_mode can be used for power down, adc sleep, etc
}
// debouncing -- don't read pins until a few tens of milliseconds after the last
// pin change occurred.
ISR(PCINT0_vect) {
OCR1A = TCNT1 + 4;
// enable the interrupt
TIMSK1 = (1<<OCIE1A) | (1<<OCIE1B);
}
// debouncing timer
ISR(TIM1_COMPA_vect) {
// disable debounce interrupt
TIMSK1 = (1<<OCIE1B);
read_pins();
act();
}
// longer timers
ISR(TIM1_COMPB_vect) {
act_timer();
}